From 3c25084ada6ceb93945b531504ad7402e1fab031 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:31:34 +0200 Subject: [PATCH 001/514] =?UTF-8?q?chore:=20=F0=9F=93=8C=20Updated=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.lock | 1079 +++++++++++++++++++++++++------------------------ 1 file changed, 544 insertions(+), 535 deletions(-) diff --git a/composer.lock b/composer.lock index 7916f4433..f01833cca 100644 --- a/composer.lock +++ b/composer.lock @@ -130,24 +130,25 @@ }, { "name": "biscolab/laravel-recaptcha", - "version": "5.2.0", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/biscolab/laravel-recaptcha.git", - "reference": "30175f16f77d5439d0e058e4dd3b2071dfe4c269" + "reference": "1bab726402d5376553a439b88a0faa07e84488fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/biscolab/laravel-recaptcha/zipball/30175f16f77d5439d0e058e4dd3b2071dfe4c269", - "reference": "30175f16f77d5439d0e058e4dd3b2071dfe4c269", + "url": "https://api.github.com/repos/biscolab/laravel-recaptcha/zipball/1bab726402d5376553a439b88a0faa07e84488fd", + "reference": "1bab726402d5376553a439b88a0faa07e84488fd", "shasum": "" }, "require": { - "laravel/framework": "^7.0|^8.0", + "illuminate/routing": "^7.0|^8.0|^9.0", + "illuminate/support": "^7.0|^8.0|^9.0", "php": "^7.3|^8.0" }, "require-dev": { - "orchestra/testbench": "5.*|6.*", + "orchestra/testbench": "5.*|6.*|^7.0", "phpunit/phpunit": "^9.1" }, "suggest": { @@ -165,12 +166,12 @@ } }, "autoload": { - "psr-4": { - "Biscolab\\ReCaptcha\\": "src/" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Biscolab\\ReCaptcha\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -194,9 +195,9 @@ ], "support": { "issues": "https://github.com/biscolab/laravel-recaptcha/issues", - "source": "https://github.com/biscolab/laravel-recaptcha/tree/v5.2.0" + "source": "https://github.com/biscolab/laravel-recaptcha/tree/v5.4.0" }, - "time": "2022-01-15T14:51:25+00:00" + "time": "2022-05-07T12:52:46+00:00" }, { "name": "brick/math", @@ -335,16 +336,16 @@ }, { "name": "doctrine/cache", - "version": "2.1.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce" + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce", - "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce", + "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", + "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", "shasum": "" }, "require": { @@ -354,18 +355,12 @@ "doctrine/common": ">2.2,<2.4" }, "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^8.0", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "predis/predis": "~1.0", + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev", - "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6" }, "type": "library", "autoload": { @@ -414,7 +409,7 @@ ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.1.1" + "source": "https://github.com/doctrine/cache/tree/2.2.0" }, "funding": [ { @@ -430,26 +425,26 @@ "type": "tidelift" } ], - "time": "2021-07-17T14:49:29+00:00" + "time": "2022-05-20T20:07:39+00:00" }, { "name": "doctrine/dbal", - "version": "3.3.2", + "version": "3.3.6", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "35eae239ef515d55ebb24e9d4715cad09a4f58ed" + "reference": "9e7f76dd1cde81c62574fdffa5a9c655c847ad21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/35eae239ef515d55ebb24e9d4715cad09a4f58ed", - "reference": "35eae239ef515d55ebb24e9d4715cad09a4f58ed", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/9e7f76dd1cde81c62574fdffa5a9c655c847ad21", + "reference": "9e7f76dd1cde81c62574fdffa5a9c655c847ad21", "shasum": "" }, "require": { "composer-runtime-api": "^2", "doctrine/cache": "^1.11|^2.0", - "doctrine/deprecations": "^0.5.3", + "doctrine/deprecations": "^0.5.3|^1", "doctrine/event-manager": "^1.0", "php": "^7.3 || ^8.0", "psr/cache": "^1|^2|^3", @@ -457,15 +452,15 @@ }, "require-dev": { "doctrine/coding-standard": "9.0.0", - "jetbrains/phpstorm-stubs": "2021.1", - "phpstan/phpstan": "1.4.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "9.5.11", + "jetbrains/phpstorm-stubs": "2022.1", + "phpstan/phpstan": "1.6.3", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "9.5.20", "psalm/plugin-phpunit": "0.16.1", "squizlabs/php_codesniffer": "3.6.2", "symfony/cache": "^5.2|^6.0", "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0", - "vimeo/psalm": "4.16.1" + "vimeo/psalm": "4.23.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -525,7 +520,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.3.2" + "source": "https://github.com/doctrine/dbal/tree/3.3.6" }, "funding": [ { @@ -541,29 +536,29 @@ "type": "tidelift" } ], - "time": "2022-02-05T16:33:45+00:00" + "time": "2022-05-02T17:21:01+00:00" }, { "name": "doctrine/deprecations", - "version": "v0.5.3", + "version": "v1.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", - "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", "shasum": "" }, "require": { "php": "^7.1|^8.0" }, "require-dev": { - "doctrine/coding-standard": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^7.0|^8.0|^9.0", - "psr/log": "^1.0" + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -582,9 +577,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" }, - "time": "2021-03-21T12:59:47+00:00" + "time": "2022-05-02T15:47:09+00:00" }, { "name": "doctrine/event-manager", @@ -773,16 +768,16 @@ }, { "name": "doctrine/lexer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c" + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c", - "reference": "9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", "shasum": "" }, "require": { @@ -790,7 +785,7 @@ }, "require-dev": { "doctrine/coding-standard": "^9.0", - "phpstan/phpstan": "1.3", + "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", "vimeo/psalm": "^4.11" }, @@ -829,7 +824,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.2" + "source": "https://github.com/doctrine/lexer/tree/1.2.3" }, "funding": [ { @@ -845,20 +840,20 @@ "type": "tidelift" } ], - "time": "2022-01-12T08:27:12+00:00" + "time": "2022-02-28T11:07:21+00:00" }, { "name": "dompdf/dompdf", - "version": "v1.2.0", + "version": "v1.2.2", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "60b704331479a69e9bcdb3496da2315b5c4f94fd" + "reference": "5031045d9640b38cfc14aac9667470df09c9e090" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/60b704331479a69e9bcdb3496da2315b5c4f94fd", - "reference": "60b704331479a69e9bcdb3496da2315b5c4f94fd", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5031045d9640b38cfc14aac9667470df09c9e090", + "reference": "5031045d9640b38cfc14aac9667470df09c9e090", "shasum": "" }, "require": { @@ -869,6 +864,8 @@ "php": "^7.1 || ^8.0" }, "require-dev": { + "ext-json": "*", + "ext-zip": "*", "mockery/mockery": "^1.3", "phpunit/phpunit": "^7.5 || ^8 || ^9", "squizlabs/php_codesniffer": "^3.5" @@ -910,9 +907,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v1.2.0" + "source": "https://github.com/dompdf/dompdf/tree/v1.2.2" }, - "time": "2022-02-07T13:02:10+00:00" + "time": "2022-04-27T13:50:54+00:00" }, { "name": "dragonmantank/cron-expression", @@ -1103,25 +1100,23 @@ }, { "name": "fruitcake/laravel-cors", - "version": "v2.0.5", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/fruitcake/laravel-cors.git", - "reference": "3a066e5cac32e2d1cdaacd6b961692778f37b5fc" + "reference": "783a74f5e3431d7b9805be8afb60fd0a8f743534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/3a066e5cac32e2d1cdaacd6b961692778f37b5fc", - "reference": "3a066e5cac32e2d1cdaacd6b961692778f37b5fc", + "url": "https://api.github.com/repos/fruitcake/laravel-cors/zipball/783a74f5e3431d7b9805be8afb60fd0a8f743534", + "reference": "783a74f5e3431d7b9805be8afb60fd0a8f743534", "shasum": "" }, "require": { "asm89/stack-cors": "^2.0.1", "illuminate/contracts": "^6|^7|^8|^9", "illuminate/support": "^6|^7|^8|^9", - "php": ">=7.2", - "symfony/http-foundation": "^4|^5|^6", - "symfony/http-kernel": "^4.3.4|^5|^6" + "php": ">=7.2" }, "require-dev": { "laravel/framework": "^6|^7.24|^8", @@ -1132,7 +1127,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" }, "laravel": { "providers": [ @@ -1168,7 +1163,7 @@ ], "support": { "issues": "https://github.com/fruitcake/laravel-cors/issues", - "source": "https://github.com/fruitcake/laravel-cors/tree/v2.0.5" + "source": "https://github.com/fruitcake/laravel-cors/tree/v2.2.0" }, "funding": [ { @@ -1180,7 +1175,7 @@ "type": "github" } ], - "time": "2022-01-03T14:53:04+00:00" + "time": "2022-02-23T14:25:13+00:00" }, { "name": "graham-campbell/result-type", @@ -1246,16 +1241,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.4.1", + "version": "7.4.4", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79" + "reference": "e3ff079b22820c2029d4c2a87796b6a0b8716ad8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/e3ff079b22820c2029d4c2a87796b6a0b8716ad8", + "reference": "e3ff079b22820c2029d4c2a87796b6a0b8716ad8", "shasum": "" }, "require": { @@ -1288,12 +1283,12 @@ } }, "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1350,7 +1345,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.1" + "source": "https://github.com/guzzle/guzzle/tree/7.4.4" }, "funding": [ { @@ -1366,7 +1361,7 @@ "type": "tidelift" } ], - "time": "2021-12-06T18:43:05+00:00" + "time": "2022-06-09T21:39:15+00:00" }, { "name": "guzzlehttp/promises", @@ -1395,12 +1390,12 @@ } }, "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - }, "files": [ "src/functions_include.php" - ] + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1454,16 +1449,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.1.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72" + "reference": "83260bb50b8fc753c72d14dc1621a2dac31877ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72", - "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/83260bb50b8fc753c72d14dc1621a2dac31877ee", + "reference": "83260bb50b8fc753c72d14dc1621a2dac31877ee", "shasum": "" }, "require": { @@ -1487,7 +1482,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "2.3-dev" } }, "autoload": { @@ -1549,7 +1544,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.1.0" + "source": "https://github.com/guzzle/psr7/tree/2.3.0" }, "funding": [ { @@ -1565,7 +1560,7 @@ "type": "tidelift" } ], - "time": "2021-10-06T17:43:30+00:00" + "time": "2022-06-09T08:26:02+00:00" }, { "name": "hidehalo/nanoid-php", @@ -1626,27 +1621,29 @@ }, { "name": "kkomelin/laravel-translatable-string-exporter", - "version": "1.14.0", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/kkomelin/laravel-translatable-string-exporter.git", - "reference": "9dce1e5f8ed59a1b58e77ec7d84f1427d5e29f0a" + "reference": "0425f2c3add32df852c002b11bffe72c9c67ec89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kkomelin/laravel-translatable-string-exporter/zipball/9dce1e5f8ed59a1b58e77ec7d84f1427d5e29f0a", - "reference": "9dce1e5f8ed59a1b58e77ec7d84f1427d5e29f0a", + "url": "https://api.github.com/repos/kkomelin/laravel-translatable-string-exporter/zipball/0425f2c3add32df852c002b11bffe72c9c67ec89", + "reference": "0425f2c3add32df852c002b11bffe72c9c67ec89", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/support": "^5.4|^6|^7|^8", - "illuminate/translation": "^5.4|^6|^7|^8", - "php": ">=5.4.0", - "symfony/finder": "^3.2|^4|^5" + "illuminate/support": "^5.4|^6|^7|^8|^9", + "illuminate/translation": "^5.4|^6|^7|^8|^9", + "php": "^7.2|^8.0", + "symfony/finder": "^3.2|^4|^5|^6" }, "require-dev": { - "orchestra/testbench": "^3.4|^4.0|^5.0|^6.0" + "nunomaduro/larastan": "^1.0|^2.0", + "orchestra/testbench": "^3.4|^4.0|^5.0|^6.0|^7.0", + "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0" }, "type": "library", "extra": { @@ -1677,26 +1674,28 @@ "exporter", "json", "laravel", + "localization", + "translation", "translations" ], "support": { "issues": "https://github.com/kkomelin/laravel-translatable-string-exporter/issues", - "source": "https://github.com/kkomelin/laravel-translatable-string-exporter/tree/1.14.0" + "source": "https://github.com/kkomelin/laravel-translatable-string-exporter/tree/1.17.0" }, - "time": "2021-08-08T06:48:21+00:00" + "time": "2022-06-13T07:13:55+00:00" }, { "name": "laravel/framework", - "version": "v8.82.0", + "version": "v8.83.16", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "411d5243c58cbf12b0fc89cab1ceb50088968c27" + "reference": "6be5abd144faf517879af7298e9d79f06f250f75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/411d5243c58cbf12b0fc89cab1ceb50088968c27", - "reference": "411d5243c58cbf12b0fc89cab1ceb50088968c27", + "url": "https://api.github.com/repos/laravel/framework/zipball/6be5abd144faf517879af7298e9d79f06f250f75", + "reference": "6be5abd144faf517879af7298e9d79f06f250f75", "shasum": "" }, "require": { @@ -1856,20 +1855,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2022-02-01T16:13:57+00:00" + "time": "2022-06-07T15:09:06+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "65c9faf50d567b65d81764a44526545689e3fe63" + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/65c9faf50d567b65d81764a44526545689e3fe63", - "reference": "65c9faf50d567b65d81764a44526545689e3fe63", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540", + "reference": "09f0e9fb61829f628205b7c94906c28740ff9540", "shasum": "" }, "require": { @@ -1915,20 +1914,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2022-02-01T16:29:39+00:00" + "time": "2022-05-16T17:09:47+00:00" }, { "name": "laravel/socialite", - "version": "v5.5.0", + "version": "v5.5.2", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "cb5b5538c207efa19aa5d7f46cd76acb03ec3055" + "reference": "68afb03259b82d898c68196cbcacd48596a9dd72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/cb5b5538c207efa19aa5d7f46cd76acb03ec3055", - "reference": "cb5b5538c207efa19aa5d7f46cd76acb03ec3055", + "url": "https://api.github.com/repos/laravel/socialite/zipball/68afb03259b82d898c68196cbcacd48596a9dd72", + "reference": "68afb03259b82d898c68196cbcacd48596a9dd72", "shasum": "" }, "require": { @@ -1984,20 +1983,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2022-02-01T16:31:36+00:00" + "time": "2022-03-10T15:26:19+00:00" }, { "name": "laravel/tinker", - "version": "v2.7.0", + "version": "v2.7.2", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "5f2f9815b7631b9f586a3de7933c25f9327d4073" + "reference": "dff39b661e827dae6e092412f976658df82dbac5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/5f2f9815b7631b9f586a3de7933c25f9327d4073", - "reference": "5f2f9815b7631b9f586a3de7933c25f9327d4073", + "url": "https://api.github.com/repos/laravel/tinker/zipball/dff39b661e827dae6e092412f976658df82dbac5", + "reference": "dff39b661e827dae6e092412f976658df82dbac5", "shasum": "" }, "require": { @@ -2050,28 +2049,28 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.7.0" + "source": "https://github.com/laravel/tinker/tree/v2.7.2" }, - "time": "2022-01-10T08:52:49+00:00" + "time": "2022-03-23T12:38:24+00:00" }, { "name": "laravel/ui", - "version": "v3.4.2", + "version": "v3.4.6", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "e01198123f7f4369d13c1f83a897c3f5e97fc9f4" + "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/e01198123f7f4369d13c1f83a897c3f5e97fc9f4", - "reference": "e01198123f7f4369d13c1f83a897c3f5e97fc9f4", + "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c", + "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c", "shasum": "" }, "require": { "illuminate/console": "^8.42|^9.0", "illuminate/filesystem": "^8.42|^9.0", - "illuminate/support": "^8.42|^9.0", + "illuminate/support": "^8.82|^9.0", "illuminate/validation": "^8.42|^9.0", "php": "^7.3|^8.0" }, @@ -2111,9 +2110,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v3.4.2" + "source": "https://github.com/laravel/ui/tree/v3.4.6" }, - "time": "2022-01-25T20:15:56+00:00" + "time": "2022-05-20T13:38:08+00:00" }, { "name": "laraveldaily/laravel-invoices", @@ -2182,16 +2181,16 @@ }, { "name": "league/commonmark", - "version": "2.2.1", + "version": "2.3.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a" + "reference": "0da1dca5781dd3cfddbe328224d9a7a62571addc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a", - "reference": "f8afb78f087777b040e0ab8a6b6ca93f6fc3f18a", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/0da1dca5781dd3cfddbe328224d9a7a62571addc", + "reference": "0da1dca5781dd3cfddbe328224d9a7a62571addc", "shasum": "" }, "require": { @@ -2200,17 +2199,19 @@ "php": "^7.4 || ^8.0", "psr/event-dispatcher": "^1.0", "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/polyfill-php80": "^1.15" + "symfony/polyfill-php80": "^1.16" }, "require-dev": { "cebe/markdown": "^1.0", "commonmark/cmark": "0.30.0", "commonmark/commonmark.js": "0.30.0", "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", "erusev/parsedown": "^1.0", "ext-json": "*", "github/gfm": "0.29.0", "michelf/php-markdown": "^1.4", + "nyholm/psr7": "^1.5", "phpstan/phpstan": "^0.12.88 || ^1.0.0", "phpunit/phpunit": "^9.5.5", "scrutinizer/ocular": "^1.8.1", @@ -2225,7 +2226,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.3-dev" + "dev-main": "2.4-dev" } }, "autoload": { @@ -2282,7 +2283,7 @@ "type": "tidelift" } ], - "time": "2022-01-25T14:37:33+00:00" + "time": "2022-06-07T21:28:26+00:00" }, { "name": "league/config", @@ -2462,16 +2463,16 @@ }, { "name": "league/mime-type-detection", - "version": "1.9.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69" + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/aa70e813a6ad3d1558fc927863d47309b4c23e69", - "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", "shasum": "" }, "require": { @@ -2502,7 +2503,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.9.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" }, "funding": [ { @@ -2514,20 +2515,20 @@ "type": "tidelift" } ], - "time": "2021-11-21T11:48:40+00:00" + "time": "2022-04-17T13:12:02+00:00" }, { "name": "league/oauth1-client", - "version": "v1.10.0", + "version": "v1.10.1", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth1-client.git", - "reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc" + "reference": "d6365b901b5c287dd41f143033315e2f777e1167" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/88dd16b0cff68eb9167bfc849707d2c40ad91ddc", - "reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167", + "reference": "d6365b901b5c287dd41f143033315e2f777e1167", "shasum": "" }, "require": { @@ -2588,22 +2589,22 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth1-client/issues", - "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.0" + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1" }, - "time": "2021-08-15T23:05:49+00:00" + "time": "2022-04-15T14:02:14+00:00" }, { "name": "monolog/monolog", - "version": "2.3.5", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd4380d6fc37626e2f799f29d91195040137eba9" + "reference": "5579edf28aee1190a798bfa5be8bc16c563bd524" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd4380d6fc37626e2f799f29d91195040137eba9", - "reference": "fd4380d6fc37626e2f799f29d91195040137eba9", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5579edf28aee1190a798bfa5be8bc16c563bd524", + "reference": "5579edf28aee1190a798bfa5be8bc16c563bd524", "shasum": "" }, "require": { @@ -2616,18 +2617,23 @@ "require-dev": { "aws/aws-sdk-php": "^2.4.9 || ^3.0", "doctrine/couchdb": "~1.0@dev", - "elasticsearch/elasticsearch": "^7", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", "graylog2/gelf-php": "^1.4.2", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", "php-console/php-console": "^3.1.3", - "phpspec/prophecy": "^1.6.1", + "phpspec/prophecy": "^1.15", "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5", + "phpunit/phpunit": "^8.5.14", "predis/predis": "^1.1", - "rollbar/rollbar": "^1.3", - "ruflin/elastica": ">=0.90@dev", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", @@ -2677,7 +2683,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.3.5" + "source": "https://github.com/Seldaek/monolog/tree/2.7.0" }, "funding": [ { @@ -2689,20 +2695,20 @@ "type": "tidelift" } ], - "time": "2021-10-01T21:08:31+00:00" + "time": "2022-06-09T08:59:12+00:00" }, { "name": "nesbot/carbon", - "version": "2.56.0", + "version": "2.58.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "626ec8cbb724cd3c3400c3ed8f730545b744e3f4" + "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/626ec8cbb724cd3c3400c3ed8f730545b744e3f4", - "reference": "626ec8cbb724cd3c3400c3ed8f730545b744e3f4", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/97a34af22bde8d0ac20ab34b29d7bfe360902055", + "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055", "shasum": "" }, "require": { @@ -2720,7 +2726,8 @@ "phpmd/phpmd": "^2.9", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12.54 || ^1.0", - "phpunit/phpunit": "^7.5.20 || ^8.5.14", + "phpunit/php-file-iterator": "^2.0.5", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", "squizlabs/php_codesniffer": "^3.4" }, "bin": [ @@ -2785,7 +2792,7 @@ "type": "tidelift" } ], - "time": "2022-01-21T17:08:38+00:00" + "time": "2022-04-25T19:31:17+00:00" }, { "name": "nette/schema", @@ -2936,16 +2943,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.13.2", + "version": "v4.14.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077" + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077", - "reference": "210577fe3cf7badcc5814d99455df46564f3c077", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", + "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", "shasum": "" }, "require": { @@ -2986,9 +2993,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" }, - "time": "2021-11-30T19:35:32+00:00" + "time": "2022-05-31T20:59:12+00:00" }, { "name": "opis/closure", @@ -3018,12 +3025,12 @@ } }, "autoload": { - "psr-4": { - "Opis\\Closure\\": "src/" - }, "files": [ "functions.php" - ] + ], + "psr-4": { + "Opis\\Closure\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3128,8 +3135,8 @@ "type": "library", "autoload": { "psr-4": { - "PayPalCheckoutSdk\\": "lib/PayPalCheckoutSdk", - "Sample\\": "samples/" + "Sample\\": "samples/", + "PayPalCheckoutSdk\\": "lib/PayPalCheckoutSdk" } }, "notification-url": "https://packagist.org/downloads/", @@ -3300,25 +3307,25 @@ }, { "name": "phenx/php-svg-lib", - "version": "0.4.0", + "version": "0.4.1", "source": { "type": "git", "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "3ffbbb037f0871c3a819e90cff8b36dd7e656189" + "reference": "4498b5df7b08e8469f0f8279651ea5de9626ed02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/3ffbbb037f0871c3a819e90cff8b36dd7e656189", - "reference": "3ffbbb037f0871c3a819e90cff8b36dd7e656189", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/4498b5df7b08e8469f0f8279651ea5de9626ed02", + "reference": "4498b5df7b08e8469f0f8279651ea5de9626ed02", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^7.4 || ^8.0", + "php": "^7.1 || ^7.2 || ^7.3 || ^7.4 || ^8.0", "sabberworm/php-css-parser": "^8.4" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" }, "type": "library", "autoload": { @@ -3340,9 +3347,9 @@ "homepage": "https://github.com/PhenX/php-svg-lib", "support": { "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/0.4.0" + "source": "https://github.com/dompdf/php-svg-lib/tree/0.4.1" }, - "time": "2021-12-17T14:08:35+00:00" + "time": "2022-03-07T12:52:04+00:00" }, { "name": "phpoption/phpoption", @@ -3825,16 +3832,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.1", + "version": "v0.11.5", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "570292577277f06f590635381a7f761a6cf4f026" + "reference": "c23686f9c48ca202710dbb967df8385a952a2daf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/570292577277f06f590635381a7f761a6cf4f026", - "reference": "570292577277f06f590635381a7f761a6cf4f026", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/c23686f9c48ca202710dbb967df8385a952a2daf", + "reference": "c23686f9c48ca202710dbb967df8385a952a2daf", "shasum": "" }, "require": { @@ -3845,16 +3852,17 @@ "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.2", - "hoa/console": "3.17.05.02" + "bamarni/composer-bin-plugin": "^1.2" }, "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", "ext-pdo-sqlite": "The doc command requires SQLite to work.", "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", - "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." }, "bin": [ "bin/psysh" @@ -3894,9 +3902,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.1" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.5" }, - "time": "2022-01-03T13:58:38+00:00" + "time": "2022-05-27T18:03:49+00:00" }, { "name": "ralouphie/getallheaders", @@ -4023,25 +4031,24 @@ }, { "name": "ramsey/uuid", - "version": "4.2.3", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" + "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", - "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", + "reference": "8505afd4fea63b81a85d3b7b53ac3cb8dc347c28", "shasum": "" }, "require": { "brick/math": "^0.8 || ^0.9", + "ext-ctype": "*", "ext-json": "*", - "php": "^7.2 || ^8.0", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php80": "^1.14" + "php": "^8.0", + "ramsey/collection": "^1.0" }, "replace": { "rhumsaa/uuid": "self.version" @@ -4078,20 +4085,17 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "4.x-dev" - }, "captainhook": { "force-install": true } }, "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4105,7 +4109,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.2.3" + "source": "https://github.com/ramsey/uuid/tree/4.3.1" }, "funding": [ { @@ -4117,7 +4121,7 @@ "type": "tidelift" } ], - "time": "2021-09-25T23:10:38+00:00" + "time": "2022-03-27T21:42:02+00:00" }, { "name": "sabberworm/php-css-parser", @@ -4521,16 +4525,16 @@ }, { "name": "stripe/stripe-php", - "version": "v7.113.0", + "version": "v7.128.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "1aef1ccffad48f39952073e0ed53cb8f3f1b1d8c" + "reference": "c704949c49b72985c76cc61063aa26fefbd2724e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/1aef1ccffad48f39952073e0ed53cb8f3f1b1d8c", - "reference": "1aef1ccffad48f39952073e0ed53cb8f3f1b1d8c", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/c704949c49b72985c76cc61063aa26fefbd2724e", + "reference": "c704949c49b72985c76cc61063aa26fefbd2724e", "shasum": "" }, "require": { @@ -4575,9 +4579,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v7.113.0" + "source": "https://github.com/stripe/stripe-php/tree/v7.128.0" }, - "time": "2022-02-03T23:46:29+00:00" + "time": "2022-05-05T17:18:02+00:00" }, { "name": "swiftmailer/swiftmailer", @@ -4657,16 +4661,16 @@ }, { "name": "symfony/console", - "version": "v5.4.3", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "a2a86ec353d825c75856c6fd14fac416a7bdb6b8" + "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/a2a86ec353d825c75856c6fd14fac416a7bdb6b8", - "reference": "a2a86ec353d825c75856c6fd14fac416a7bdb6b8", + "url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb", + "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb", "shasum": "" }, "require": { @@ -4736,7 +4740,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.3" + "source": "https://github.com/symfony/console/tree/v5.4.9" }, "funding": [ { @@ -4752,7 +4756,7 @@ "type": "tidelift" } ], - "time": "2022-01-26T16:28:35+00:00" + "time": "2022-05-18T06:17:34+00:00" }, { "name": "symfony/css-selector", @@ -4821,16 +4825,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced" + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", - "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", "shasum": "" }, "require": { @@ -4868,7 +4872,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1" }, "funding": [ { @@ -4884,20 +4888,20 @@ "type": "tidelift" } ], - "time": "2021-11-01T23:48:49+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/error-handler", - "version": "v5.4.3", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c4ffc2cd919950d13c8c9ce32a70c70214c3ffc5" + "reference": "c116cda1f51c678782768dce89a45f13c949455d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c4ffc2cd919950d13c8c9ce32a70c70214c3ffc5", - "reference": "c4ffc2cd919950d13c8c9ce32a70c70214c3ffc5", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c116cda1f51c678782768dce89a45f13c949455d", + "reference": "c116cda1f51c678782768dce89a45f13c949455d", "shasum": "" }, "require": { @@ -4939,7 +4943,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.4.3" + "source": "https://github.com/symfony/error-handler/tree/v5.4.9" }, "funding": [ { @@ -4955,20 +4959,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-05-21T13:57:48+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.0.3", + "version": "v6.0.9", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "6472ea2dd415e925b90ca82be64b8bc6157f3934" + "reference": "5c85b58422865d42c6eb46f7693339056db098a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/6472ea2dd415e925b90ca82be64b8bc6157f3934", - "reference": "6472ea2dd415e925b90ca82be64b8bc6157f3934", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5c85b58422865d42c6eb46f7693339056db098a8", + "reference": "5c85b58422865d42c6eb46f7693339056db098a8", "shasum": "" }, "require": { @@ -5022,7 +5026,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.3" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.9" }, "funding": [ { @@ -5038,20 +5042,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-05-05T16:45:52+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "aa5422287b75594b90ee9cd807caf8f0df491385" + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/aa5422287b75594b90ee9cd807caf8f0df491385", - "reference": "aa5422287b75594b90ee9cd807caf8f0df491385", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7bc61cc2db649b4637d331240c5346dcc7708051", + "reference": "7bc61cc2db649b4637d331240c5346dcc7708051", "shasum": "" }, "require": { @@ -5101,7 +5105,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.0.1" }, "funding": [ { @@ -5117,20 +5121,20 @@ "type": "tidelift" } ], - "time": "2021-07-15T12:33:35+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/finder", - "version": "v5.4.3", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "231313534dded84c7ecaa79d14bc5da4ccb69b7d" + "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/231313534dded84c7ecaa79d14bc5da4ccb69b7d", - "reference": "231313534dded84c7ecaa79d14bc5da4ccb69b7d", + "url": "https://api.github.com/repos/symfony/finder/zipball/9b630f3427f3ebe7cd346c277a1408b00249dad9", + "reference": "9b630f3427f3ebe7cd346c277a1408b00249dad9", "shasum": "" }, "require": { @@ -5164,7 +5168,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.3" + "source": "https://github.com/symfony/finder/tree/v5.4.8" }, "funding": [ { @@ -5180,20 +5184,20 @@ "type": "tidelift" } ], - "time": "2022-01-26T16:34:36+00:00" + "time": "2022-04-15T08:07:45+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.4.3", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ef409ff341a565a3663157d4324536746d49a0c7" + "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ef409ff341a565a3663157d4324536746d49a0c7", - "reference": "ef409ff341a565a3663157d4324536746d49a0c7", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b0d0e4aca38d57605dcd11e2416994b38774522", + "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522", "shasum": "" }, "require": { @@ -5237,7 +5241,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.3" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.9" }, "funding": [ { @@ -5253,20 +5257,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-05-17T15:07:29+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.4", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "49f40347228c773688a0488feea0175aa7f4d268" + "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/49f40347228c773688a0488feea0175aa7f4d268", - "reference": "49f40347228c773688a0488feea0175aa7f4d268", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/34b121ad3dc761f35fe1346d2f15618f8cbf77f8", + "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8", "shasum": "" }, "require": { @@ -5349,7 +5353,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/v5.4.4" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.9" }, "funding": [ { @@ -5365,20 +5369,20 @@ "type": "tidelift" } ], - "time": "2022-01-29T18:08:07+00:00" + "time": "2022-05-27T07:09:08+00:00" }, { "name": "symfony/intl", - "version": "v5.4.3", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "29e326276b2455bcfa4ce02abcf7689e884acdac" + "reference": "b9e17d7ab867ce99f89950ebced0fa91076ba12b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/29e326276b2455bcfa4ce02abcf7689e884acdac", - "reference": "29e326276b2455bcfa4ce02abcf7689e884acdac", + "url": "https://api.github.com/repos/symfony/intl/zipball/b9e17d7ab867ce99f89950ebced0fa91076ba12b", + "reference": "b9e17d7ab867ce99f89950ebced0fa91076ba12b", "shasum": "" }, "require": { @@ -5391,15 +5395,15 @@ }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { "Symfony\\Component\\Intl\\": "" }, "classmap": [ "Resources/stubs" ], - "files": [ - "Resources/functions.php" - ], "exclude-from-classmap": [ "/Tests/" ] @@ -5437,7 +5441,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v5.4.3" + "source": "https://github.com/symfony/intl/tree/v5.4.8" }, "funding": [ { @@ -5453,20 +5457,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-04-07T09:39:59+00:00" }, { "name": "symfony/mime", - "version": "v5.4.3", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "e1503cfb5c9a225350f549d3bb99296f4abfb80f" + "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/e1503cfb5c9a225350f549d3bb99296f4abfb80f", - "reference": "e1503cfb5c9a225350f549d3bb99296f4abfb80f", + "url": "https://api.github.com/repos/symfony/mime/zipball/2b3802a24e48d0cfccf885173d2aac91e73df92e", + "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e", "shasum": "" }, "require": { @@ -5520,7 +5524,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.3" + "source": "https://github.com/symfony/mime/tree/v5.4.9" }, "funding": [ { @@ -5536,20 +5540,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-05-21T10:24:18+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "30885182c981ab175d4d034db0f6f469898070ab" + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab", - "reference": "30885182c981ab175d4d034db0f6f469898070ab", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", "shasum": "" }, "require": { @@ -5564,7 +5568,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5572,12 +5576,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5602,7 +5606,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" }, "funding": [ { @@ -5618,20 +5622,20 @@ "type": "tidelift" } ], - "time": "2021-10-20T20:35:02+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40" + "reference": "143f1881e655bebca1312722af8068de235ae5dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f1aed619e28cb077fc83fac8c4c0383578356e40", - "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/143f1881e655bebca1312722af8068de235ae5dc", + "reference": "143f1881e655bebca1312722af8068de235ae5dc", "shasum": "" }, "require": { @@ -5646,7 +5650,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5685,7 +5689,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.26.0" }, "funding": [ { @@ -5701,20 +5705,20 @@ "type": "tidelift" } ], - "time": "2022-01-04T09:04:05+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783" + "reference": "433d05519ce6990bf3530fba6957499d327395c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783", - "reference": "81b86b50cf841a64252b439e738e97f4a34e2783", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", + "reference": "433d05519ce6990bf3530fba6957499d327395c2", "shasum": "" }, "require": { @@ -5726,7 +5730,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5766,7 +5770,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" }, "funding": [ { @@ -5782,20 +5786,20 @@ "type": "tidelift" } ], - "time": "2021-11-23T21:10:46+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "749045c69efb97c70d25d7463abba812e91f3a44" + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44", - "reference": "749045c69efb97c70d25d7463abba812e91f3a44", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", "shasum": "" }, "require": { @@ -5809,7 +5813,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5853,7 +5857,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" }, "funding": [ { @@ -5869,20 +5873,20 @@ "type": "tidelift" } ], - "time": "2021-09-14T14:02:44+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8" + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8", - "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", "shasum": "" }, "require": { @@ -5894,7 +5898,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5937,7 +5941,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" }, "funding": [ { @@ -5953,20 +5957,20 @@ "type": "tidelift" } ], - "time": "2021-02-19T12:13:01+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825" + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825", - "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", "shasum": "" }, "require": { @@ -5981,7 +5985,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -5989,12 +5993,12 @@ } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6020,7 +6024,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" }, "funding": [ { @@ -6036,20 +6040,20 @@ "type": "tidelift" } ], - "time": "2021-11-30T18:21:41+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", - "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", + "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", "shasum": "" }, "require": { @@ -6058,7 +6062,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6096,7 +6100,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0" }, "funding": [ { @@ -6112,20 +6116,20 @@ "type": "tidelift" } ], - "time": "2021-05-27T09:17:38+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5" + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5", - "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", + "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", "shasum": "" }, "require": { @@ -6134,7 +6138,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6175,7 +6179,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" }, "funding": [ { @@ -6191,20 +6195,20 @@ "type": "tidelift" } ], - "time": "2021-06-05T21:20:04+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9" + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/57b712b08eddb97c762a8caa32c84e037892d2e9", - "reference": "57b712b08eddb97c762a8caa32c84e037892d2e9", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", "shasum": "" }, "require": { @@ -6213,7 +6217,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6258,7 +6262,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" }, "funding": [ { @@ -6274,20 +6278,20 @@ "type": "tidelift" } ], - "time": "2021-09-13T13:58:33+00:00" + "time": "2022-05-10T07:21:04+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.24.0", + "version": "v1.26.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f" + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", - "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", "shasum": "" }, "require": { @@ -6296,7 +6300,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.23-dev" + "dev-main": "1.26-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6337,7 +6341,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.24.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" }, "funding": [ { @@ -6353,20 +6357,20 @@ "type": "tidelift" } ], - "time": "2021-09-13T13:58:11+00:00" + "time": "2022-05-24T11:49:31+00:00" }, { "name": "symfony/process", - "version": "v5.4.3", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "553f50487389a977eb31cf6b37faae56da00f753" + "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/553f50487389a977eb31cf6b37faae56da00f753", - "reference": "553f50487389a977eb31cf6b37faae56da00f753", + "url": "https://api.github.com/repos/symfony/process/zipball/597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", + "reference": "597f3fff8e3e91836bb0bd38f5718b56ddbde2f3", "shasum": "" }, "require": { @@ -6399,7 +6403,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.3" + "source": "https://github.com/symfony/process/tree/v5.4.8" }, "funding": [ { @@ -6415,20 +6419,20 @@ "type": "tidelift" } ], - "time": "2022-01-26T16:28:35+00:00" + "time": "2022-04-08T05:07:18+00:00" }, { "name": "symfony/routing", - "version": "v5.4.3", + "version": "v5.4.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "44b29c7a94e867ccde1da604792f11a469958981" + "reference": "e07817bb6244ea33ef5ad31abc4a9288bef3f2f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/44b29c7a94e867ccde1da604792f11a469958981", - "reference": "44b29c7a94e867ccde1da604792f11a469958981", + "url": "https://api.github.com/repos/symfony/routing/zipball/e07817bb6244ea33ef5ad31abc4a9288bef3f2f7", + "reference": "e07817bb6244ea33ef5ad31abc4a9288bef3f2f7", "shasum": "" }, "require": { @@ -6489,7 +6493,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.4.3" + "source": "https://github.com/symfony/routing/tree/v5.4.8" }, "funding": [ { @@ -6505,25 +6509,26 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2022-04-18T21:45:37+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.4.1", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "d664541b99d6fb0247ec5ff32e87238582236204" + "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d664541b99d6fb0247ec5ff32e87238582236204", - "reference": "d664541b99d6fb0247ec5ff32e87238582236204", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/24d9dc654b83e91aa59f9d167b131bc3b5bea24c", + "reference": "24d9dc654b83e91aa59f9d167b131bc3b5bea24c", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/container": "^1.1" + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" }, "conflict": { "ext-psr": "<1.1|>=2" @@ -6534,7 +6539,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -6571,7 +6576,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.4.1" + "source": "https://github.com/symfony/service-contracts/tree/v2.5.1" }, "funding": [ { @@ -6587,20 +6592,20 @@ "type": "tidelift" } ], - "time": "2021-11-04T16:37:19+00:00" + "time": "2022-03-13T20:07:29+00:00" }, { "name": "symfony/string", - "version": "v6.0.3", + "version": "v6.0.9", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2" + "reference": "df9f03d595aa2d446498ba92fe803a519b2c43cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/522144f0c4c004c80d56fa47e40e17028e2eefc2", - "reference": "522144f0c4c004c80d56fa47e40e17028e2eefc2", + "url": "https://api.github.com/repos/symfony/string/zipball/df9f03d595aa2d446498ba92fe803a519b2c43cc", + "reference": "df9f03d595aa2d446498ba92fe803a519b2c43cc", "shasum": "" }, "require": { @@ -6621,12 +6626,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\String\\": "" - }, "files": [ "Resources/functions.php" ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, "exclude-from-classmap": [ "/Tests/" ] @@ -6656,7 +6661,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.0.3" + "source": "https://github.com/symfony/string/tree/v6.0.9" }, "funding": [ { @@ -6672,20 +6677,20 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2022-04-22T08:18:02+00:00" }, { "name": "symfony/translation", - "version": "v6.0.3", + "version": "v6.0.9", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "71bb15335798f8c4da110911bcf2d2fead7a430d" + "reference": "9ba011309943955a3807b8236c17cff3b88f67b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/71bb15335798f8c4da110911bcf2d2fead7a430d", - "reference": "71bb15335798f8c4da110911bcf2d2fead7a430d", + "url": "https://api.github.com/repos/symfony/translation/zipball/9ba011309943955a3807b8236c17cff3b88f67b6", + "reference": "9ba011309943955a3807b8236c17cff3b88f67b6", "shasum": "" }, "require": { @@ -6751,7 +6756,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.0.3" + "source": "https://github.com/symfony/translation/tree/v6.0.9" }, "funding": [ { @@ -6767,20 +6772,20 @@ "type": "tidelift" } ], - "time": "2022-01-07T00:29:03+00:00" + "time": "2022-05-06T14:27:17+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.0.0", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "1b6ea5a7442af5a12dba3dbd6d71034b5b234e77" + "reference": "c4183fc3ef0f0510893cbeedc7718fb5cafc9ac9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/1b6ea5a7442af5a12dba3dbd6d71034b5b234e77", - "reference": "1b6ea5a7442af5a12dba3dbd6d71034b5b234e77", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/c4183fc3ef0f0510893cbeedc7718fb5cafc9ac9", + "reference": "c4183fc3ef0f0510893cbeedc7718fb5cafc9ac9", "shasum": "" }, "require": { @@ -6829,7 +6834,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.0.1" }, "funding": [ { @@ -6845,20 +6850,20 @@ "type": "tidelift" } ], - "time": "2021-09-07T12:43:40+00:00" + "time": "2022-01-02T09:55:41+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.4.3", + "version": "v5.4.9", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "970a01f208bf895c5f327ba40b72288da43adec4" + "reference": "af52239a330fafd192c773795520dc2dd62b5657" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/970a01f208bf895c5f327ba40b72288da43adec4", - "reference": "970a01f208bf895c5f327ba40b72288da43adec4", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/af52239a330fafd192c773795520dc2dd62b5657", + "reference": "af52239a330fafd192c773795520dc2dd62b5657", "shasum": "" }, "require": { @@ -6918,7 +6923,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.3" + "source": "https://github.com/symfony/var-dumper/tree/v5.4.9" }, "funding": [ { @@ -6934,7 +6939,7 @@ "type": "tidelift" } ], - "time": "2022-01-17T16:30:37+00:00" + "time": "2022-05-21T10:24:18+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7145,21 +7150,21 @@ }, { "name": "webmozart/assert", - "version": "1.10.0", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", - "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", - "symfony/polyfill-ctype": "^1.8" + "ext-ctype": "*", + "php": "^7.2 || ^8.0" }, "conflict": { "phpstan/phpstan": "<0.12.20", @@ -7197,22 +7202,22 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.10.0" + "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, - "time": "2021-03-09T10:59:23+00:00" + "time": "2022-06-03T18:03:27+00:00" }, { "name": "yajra/laravel-datatables-oracle", - "version": "v9.19.0", + "version": "v9.20.0", "source": { "type": "git", "url": "https://github.com/yajra/laravel-datatables.git", - "reference": "553482df5f68969928acc0ee1a3af032cdaaf824" + "reference": "4c22b09c8c664df5aad9f17d99c3823c0f2d84e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/553482df5f68969928acc0ee1a3af032cdaaf824", - "reference": "553482df5f68969928acc0ee1a3af032cdaaf824", + "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/4c22b09c8c664df5aad9f17d99c3823c0f2d84e2", + "reference": "4c22b09c8c664df5aad9f17d99c3823c0f2d84e2", "shasum": "" }, "require": { @@ -7272,7 +7277,7 @@ ], "support": { "issues": "https://github.com/yajra/laravel-datatables/issues", - "source": "https://github.com/yajra/laravel-datatables/tree/v9.19.0" + "source": "https://github.com/yajra/laravel-datatables/tree/v9.20.0" }, "funding": [ { @@ -7284,22 +7289,22 @@ "type": "patreon" } ], - "time": "2022-01-18T01:13:47+00:00" + "time": "2022-05-08T16:04:16+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.6.6", + "version": "v3.6.7", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "f92fe967b40b36ad1ee8ed2fd59c05ae67a1ebba" + "reference": "b96f9820aaf1ff9afe945207883149e1c7afb298" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f92fe967b40b36ad1ee8ed2fd59c05ae67a1ebba", - "reference": "f92fe967b40b36ad1ee8ed2fd59c05ae67a1ebba", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/b96f9820aaf1ff9afe945207883149e1c7afb298", + "reference": "b96f9820aaf1ff9afe945207883149e1c7afb298", "shasum": "" }, "require": { @@ -7313,7 +7318,7 @@ }, "require-dev": { "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^4|^5|^6", + "orchestra/testbench-dusk": "^4|^5|^6|^7", "phpunit/phpunit": "^8.5|^9.0", "squizlabs/php_codesniffer": "^3.5" }, @@ -7332,12 +7337,12 @@ } }, "autoload": { - "psr-4": { - "Barryvdh\\Debugbar\\": "src/" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7359,7 +7364,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.6" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.7" }, "funding": [ { @@ -7371,33 +7376,34 @@ "type": "github" } ], - "time": "2021-12-21T18:20:10+00:00" + "time": "2022-02-09T07:52:32+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", - "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^8.0", + "doctrine/coding-standard": "^9", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { @@ -7424,7 +7430,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" }, "funding": [ { @@ -7440,7 +7446,7 @@ "type": "tidelift" } ], - "time": "2020-11-10T18:47:58+00:00" + "time": "2022-03-03T08:28:38+00:00" }, { "name": "facade/flare-client-php", @@ -7476,12 +7482,12 @@ } }, "autoload": { - "psr-4": { - "Facade\\FlareClient\\": "src" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Facade\\FlareClient\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7509,16 +7515,16 @@ }, { "name": "facade/ignition", - "version": "2.17.4", + "version": "2.17.5", "source": { "type": "git", "url": "https://github.com/facade/ignition.git", - "reference": "95c80bd35ee6858e9e1439b2f6a698295eeb2070" + "reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facade/ignition/zipball/95c80bd35ee6858e9e1439b2f6a698295eeb2070", - "reference": "95c80bd35ee6858e9e1439b2f6a698295eeb2070", + "url": "https://api.github.com/repos/facade/ignition/zipball/1d71996f83c9a5a7807331b8986ac890352b7a0c", + "reference": "1d71996f83c9a5a7807331b8986ac890352b7a0c", "shasum": "" }, "require": { @@ -7558,12 +7564,12 @@ } }, "autoload": { - "psr-4": { - "Facade\\Ignition\\": "src" - }, "files": [ "src/helpers.php" - ] + ], + "psr-4": { + "Facade\\Ignition\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7583,7 +7589,7 @@ "issues": "https://github.com/facade/ignition/issues", "source": "https://github.com/facade/ignition" }, - "time": "2021-12-27T15:11:24+00:00" + "time": "2022-02-23T18:31:24+00:00" }, { "name": "facade/ignition-contracts", @@ -7829,16 +7835,16 @@ }, { "name": "laravel/sail", - "version": "v1.13.1", + "version": "v1.14.10", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "b9749028732eca8080c26d01cd88a2f3549c2e3e" + "reference": "0ea5d683af4d189071efcdb9e83946c10dab82c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/b9749028732eca8080c26d01cd88a2f3549c2e3e", - "reference": "b9749028732eca8080c26d01cd88a2f3549c2e3e", + "url": "https://api.github.com/repos/laravel/sail/zipball/0ea5d683af4d189071efcdb9e83946c10dab82c3", + "reference": "0ea5d683af4d189071efcdb9e83946c10dab82c3", "shasum": "" }, "require": { @@ -7885,29 +7891,30 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2022-01-20T15:31:25+00:00" + "time": "2022-06-09T07:10:28+00:00" }, { "name": "maximebf/debugbar", - "version": "v1.17.3", + "version": "v1.18.0", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c" + "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/e8ac3499af0ea5b440908e06cc0abe5898008b3c", - "reference": "e8ac3499af0ea5b440908e06cc0abe5898008b3c", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", + "reference": "0d44b75f3b5d6d41ae83b79c7a4bceae7fbc78b6", "shasum": "" }, "require": { "php": "^7.1|^8", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^2.6|^3|^4|^5" + "symfony/var-dumper": "^2.6|^3|^4|^5|^6" }, "require-dev": { - "phpunit/phpunit": "^7.5.20 || ^9.4.2" + "phpunit/phpunit": "^7.5.20 || ^9.4.2", + "twig/twig": "^1.38|^2.7|^3.0" }, "suggest": { "kriswallsmith/assetic": "The best way to manage assets", @@ -7948,9 +7955,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.17.3" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.18.0" }, - "time": "2021-10-19T12:33:27+00:00" + "time": "2021-12-27T18:49:48+00:00" }, { "name": "mockery/mockery", @@ -8026,28 +8033,29 @@ }, { "name": "myclabs/deep-copy", - "version": "1.10.2", + "version": "1.11.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", - "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, - "replace": { - "myclabs/deep-copy": "self.version" + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" }, "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { @@ -8072,7 +8080,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" }, "funding": [ { @@ -8080,7 +8088,7 @@ "type": "tidelift" } ], - "time": "2020-11-13T09:40:50+00:00" + "time": "2022-03-03T13:19:32+00:00" }, { "name": "nunomaduro/collision", @@ -8231,16 +8239,16 @@ }, { "name": "phar-io/version", - "version": "3.1.1", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "15a90844ad40f127afd244c0cad228de2a80052a" + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/15a90844ad40f127afd244c0cad228de2a80052a", - "reference": "15a90844ad40f127afd244c0cad228de2a80052a", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { @@ -8276,9 +8284,9 @@ "description": "Library for handling version information and constraints", "support": { "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.1.1" + "source": "https://github.com/phar-io/version/tree/3.2.1" }, - "time": "2022-02-07T21:56:48+00:00" + "time": "2022-02-21T01:04:05+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -8392,16 +8400,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706" + "reference": "77a32518733312af16a44300404e945338981de3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706", - "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", + "reference": "77a32518733312af16a44300404e945338981de3", "shasum": "" }, "require": { @@ -8436,9 +8444,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" }, - "time": "2022-01-04T19:58:01+00:00" + "time": "2022-03-15T21:29:03+00:00" }, { "name": "phpspec/prophecy", @@ -8509,16 +8517,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.10", + "version": "9.2.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687" + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d5850aaf931743067f4bfc1ae4cbd06468400687", - "reference": "d5850aaf931743067f4bfc1ae4cbd06468400687", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", "shasum": "" }, "require": { @@ -8574,7 +8582,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.10" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" }, "funding": [ { @@ -8582,7 +8590,7 @@ "type": "github" } ], - "time": "2021-12-05T09:12:13+00:00" + "time": "2022-03-07T09:28:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8827,16 +8835,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.13", + "version": "9.5.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "597cb647654ede35e43b137926dfdfef0fb11743" + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/597cb647654ede35e43b137926dfdfef0fb11743", - "reference": "597cb647654ede35e43b137926dfdfef0fb11743", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/12bc8879fb65aef2138b26fc633cb1e3620cffba", + "reference": "12bc8879fb65aef2138b26fc633cb1e3620cffba", "shasum": "" }, "require": { @@ -8852,7 +8860,7 @@ "phar-io/version": "^3.0.2", "php": ">=7.3", "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.7", + "phpunit/php-code-coverage": "^9.2.13", "phpunit/php-file-iterator": "^3.0.5", "phpunit/php-invoker": "^3.1.1", "phpunit/php-text-template": "^2.0.3", @@ -8866,7 +8874,7 @@ "sebastian/global-state": "^5.0.1", "sebastian/object-enumerator": "^4.0.3", "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^2.3.4", + "sebastian/type": "^3.0", "sebastian/version": "^3.0.2" }, "require-dev": { @@ -8914,7 +8922,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.13" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.20" }, "funding": [ { @@ -8926,7 +8934,7 @@ "type": "github" } ], - "time": "2022-01-24T07:33:35+00:00" + "time": "2022-04-01T12:37:26+00:00" }, { "name": "sebastian/cli-parser", @@ -9294,16 +9302,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.3", + "version": "5.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac" + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac", - "reference": "388b6ced16caa751030f6a69e588299fa09200ac", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", "shasum": "" }, "require": { @@ -9345,7 +9353,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" }, "funding": [ { @@ -9353,7 +9361,7 @@ "type": "github" } ], - "time": "2020-09-28T05:52:38+00:00" + "time": "2022-04-03T09:37:03+00:00" }, { "name": "sebastian/exporter", @@ -9434,16 +9442,16 @@ }, { "name": "sebastian/global-state", - "version": "5.0.3", + "version": "5.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49" + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/23bd5951f7ff26f12d4e3242864df3e08dec4e49", - "reference": "23bd5951f7ff26f12d4e3242864df3e08dec4e49", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", "shasum": "" }, "require": { @@ -9486,7 +9494,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" }, "funding": [ { @@ -9494,7 +9502,7 @@ "type": "github" } ], - "time": "2021-06-11T13:31:12+00:00" + "time": "2022-02-14T08:28:10+00:00" }, { "name": "sebastian/lines-of-code", @@ -9785,28 +9793,28 @@ }, { "name": "sebastian/type", - "version": "2.3.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", - "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", "shasum": "" }, "require": { "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.3-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -9829,7 +9837,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" }, "funding": [ { @@ -9837,7 +9845,7 @@ "type": "github" } ], - "time": "2021-06-15T12:49:02+00:00" + "time": "2022-03-15T09:54:48+00:00" }, { "name": "sebastian/version", @@ -9894,16 +9902,16 @@ }, { "name": "symfony/debug", - "version": "v4.4.37", + "version": "v4.4.41", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "5de6c6e7f52b364840e53851c126be4d71e60470" + "reference": "6637e62480b60817b9a6984154a533e8e64c6bd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/5de6c6e7f52b364840e53851c126be4d71e60470", - "reference": "5de6c6e7f52b364840e53851c126be4d71e60470", + "url": "https://api.github.com/repos/symfony/debug/zipball/6637e62480b60817b9a6984154a533e8e64c6bd5", + "reference": "6637e62480b60817b9a6984154a533e8e64c6bd5", "shasum": "" }, "require": { @@ -9942,7 +9950,7 @@ "description": "Provides tools to ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug/tree/v4.4.37" + "source": "https://github.com/symfony/debug/tree/v4.4.41" }, "funding": [ { @@ -9958,7 +9966,8 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:41:36+00:00" + "abandoned": "symfony/error-handler", + "time": "2022-04-12T15:19:55+00:00" }, { "name": "theseer/tokenizer", From 8f38b9e023c3b1f17b93fc5372cfc4423c069c33 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:32:00 +0200 Subject: [PATCH 002/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Migration?= =?UTF-8?q?=20->=20Column=20Billing=20Period?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._092704_add_billing_period_to_products.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 database/migrations/2022_06_16_092704_add_billing_period_to_products.php diff --git a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php new file mode 100644 index 000000000..4018fb473 --- /dev/null +++ b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php @@ -0,0 +1,35 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; + +class AddBillingPeriodToProducts extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('products', function (Blueprint $table) { + $table->string('billing_period')->default("hourly"); + }); + + DB::statement('UPDATE products SET billing_period="hourly"'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('products', function (Blueprint $table) { + $table->dropColumn('billing_period'); + }); + } +} From 950278c9487c540ea9e70601c4afa8f690d42210 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:55:34 +0200 Subject: [PATCH 003/514] =?UTF-8?q?fix:=20=E2=9C=A8=20Updated=20Migration?= =?UTF-8?q?=20to=20calculate=20hourly=20price=20from=20existing=20products?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2022_06_16_092704_add_billing_period_to_products.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php index 4018fb473..708e6e5e4 100644 --- a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php +++ b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php @@ -19,6 +19,14 @@ public function up() }); DB::statement('UPDATE products SET billing_period="hourly"'); + + $products = DB::table('products')->get(); + foreach ($products as $product) { + $price = $product->price; + $price = $price / 30 / 24; + DB::table('products')->where('id', $product->id)->update(['price' => $price]); + } + } /** From 8383fa03aacb375588a21cbe0b42ad2720fa8ced Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:56:05 +0200 Subject: [PATCH 004/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Added=20Migration?= =?UTF-8?q?=20->=20Updated=20user=20credits=20column=20to=20be=20decimal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6_16_094402_update_user_price_datatype.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2022_06_16_094402_update_user_price_datatype.php diff --git a/database/migrations/2022_06_16_094402_update_user_price_datatype.php b/database/migrations/2022_06_16_094402_update_user_price_datatype.php new file mode 100644 index 000000000..9b4abb9e7 --- /dev/null +++ b/database/migrations/2022_06_16_094402_update_user_price_datatype.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class UpdateUserPriceDatatype extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('users', function (Blueprint $table) { + $table->decimal('credits', 20, 10)->default(0)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->unsignedFloat('credits')->default(250)->change(); + }); + } +} From d7830eeb41b4218da25b46e807810de17964e513 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:59:17 +0200 Subject: [PATCH 005/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Migration?= =?UTF-8?q?=20->=20last=20billed=20field=20to=20servers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...95818_add_last_billed_field_to_servers.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php diff --git a/database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php b/database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php new file mode 100644 index 000000000..6b05f3a5b --- /dev/null +++ b/database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php @@ -0,0 +1,33 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; + +class AddLastBilledFieldToServers extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('servers', function (Blueprint $table) { + $table->dateTime('last_billed')->default(DB::raw('CURRENT_TIMESTAMP'))->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('last_billed'); + }); + } +} From b726326e99e00cbfbefb0e85625a075013c9e984 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 12:01:53 +0200 Subject: [PATCH 006/514] =?UTF-8?q?feat:=20=F0=9F=93=9D=20Updated=20Produc?= =?UTF-8?q?tSeeder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/seeders/Seeds/ProductSeeder.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/database/seeders/Seeds/ProductSeeder.php b/database/seeders/Seeds/ProductSeeder.php index 7664dc867..a6035c009 100644 --- a/database/seeders/Seeds/ProductSeeder.php +++ b/database/seeders/Seeds/ProductSeeder.php @@ -16,29 +16,32 @@ public function run() { Product::create([ 'name' => 'Starter', - 'description' => '64MB Ram, 1GB Disk, 1 Database, 140 credits monthly', + 'description' => '64MB Ram, 1GB Disk, 1 Database, 140 credits hourly', 'price' => 140, 'memory' => 64, 'disk' => 1000, - 'databases' => 1 + 'databases' => 1, + 'billing_period' => 'hourly' ]); Product::create([ 'name' => 'Standard', - 'description' => '128MB Ram, 2GB Disk, 2 Database, 210 credits monthly', + 'description' => '128MB Ram, 2GB Disk, 2 Database, 210 credits hourly', 'price' => 210, 'memory' => 128, 'disk' => 2000, - 'databases' => 2 + 'databases' => 2, + 'billing_period' => 'hourly' ]); Product::create([ 'name' => 'Advanced', - 'description' => '256MB Ram, 5GB Disk, 5 Database, 280 credits monthly', + 'description' => '256MB Ram, 5GB Disk, 5 Database, 280 credits hourly', 'price' => 280, 'memory' => 256, 'disk' => 5000, - 'databases' => 5 + 'databases' => 5, + 'billing_period' => 'hourly' ]); } } From 87bca5c52bbda3624df52f77055789fb824f8cb9 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 12:04:52 +0200 Subject: [PATCH 007/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20billing=5Fp?= =?UTF-8?q?eriod=20to=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Admin/ProductController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 6b855f9fe..d86f7873f 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -73,6 +73,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", + "billing_period" => "required|in:hourly,daily,monthly", ]); $disabled = !is_null($request->input('disabled')); @@ -139,6 +140,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", + "billing_period" => "required|in:hourly,daily,monthly", ]); $disabled = !is_null($request->input('disabled')); From 4581b018b0bf57a527b813f4e1f902c84ebcbb86 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 12:27:53 +0200 Subject: [PATCH 008/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20billing=20p?= =?UTF-8?q?eriod=20to=20edit,=20create=20and=20show=20of=20products?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/admin/products/create.blade.php | 55 ++++++++++++++----- resources/views/admin/products/edit.blade.php | 54 +++++++++++++----- .../views/admin/products/index.blade.php | 2 + 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index a4fb0dc1d..2470bb175 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -116,6 +116,20 @@ class="form-control @error('swap') is-invalid @enderror" @enderror </div> + <div class="form-group"> + <label for="allocations">{{__('Allocations')}}</label> + <input value="{{$product->allocations ?? old('allocations') ?? 0}}" + id="allocations" name="allocations" + type="number" + class="form-control @error('allocations') is-invalid @enderror" + required="required"> + @error('allocations') + <div class="invalid-feedback"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group"> <label for="description">{{__('Description')}} <i data-toggle="popover" data-trigger="hover" @@ -148,6 +162,34 @@ class="form-control @error('disk') is-invalid @enderror" @enderror </div> + <div class="form-group"> + <label for="billing_period">{{__('Billing Period')}} <i + data-toggle="popover" data-trigger="hover" + data-content="{{__('Period when the user will be charged for the given price')}}" + class="fas fa-info-circle"></i></label> + + <select id="billing_period" style="width:100%" class="custom-select" name="billing_period" required + autocomplete="off" @error('billing_period') is-invalid @enderror> + <option value="hourly" @if ($product->billing_period == 'hourly') selected + @endif> + {{__('Hourly')}} + </option> + <option value="daily" @if ($product->billing_period == 'daily') selected + @endif> + {{__('Daily')}} + </option> + <option value="monthly" @if ($product->billing_period == 'monthly') selected + @endif> + {{__('Monthly')}} + </option> + </select> + @error('billing_period') + <div class="invalid-feedback"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group"> <label for="minimum_credits">{{__('Minimum')}} {{ CREDITS_DISPLAY_NAME }} <i data-toggle="popover" data-trigger="hover" @@ -205,19 +247,6 @@ class="form-control @error('backups') is-invalid @enderror" </div> @enderror </div> - <div class="form-group"> - <label for="allocations">{{__('Allocations')}}</label> - <input value="{{$product->allocations ?? old('allocations') ?? 0}}" - id="allocations" name="allocations" - type="number" - class="form-control @error('allocations') is-invalid @enderror" - required="required"> - @error('allocations') - <div class="invalid-feedback"> - {{ $message }} - </div> - @enderror - </div> </div> </div> diff --git a/resources/views/admin/products/edit.blade.php b/resources/views/admin/products/edit.blade.php index 87622bd80..651e44923 100644 --- a/resources/views/admin/products/edit.blade.php +++ b/resources/views/admin/products/edit.blade.php @@ -122,7 +122,18 @@ class="form-control @error('swap') is-invalid @enderror" </div> @enderror </div> - + <div class="form-group"> + <label for="allocations">{{__('Allocations')}}</label> + <input value="{{ $product->allocations }}" id="allocations" + name="allocations" type="number" + class="form-control @error('allocations') is-invalid @enderror" + required="required"> + @error('allocations') + <div class="invalid-feedback"> + {{ $message }} + </div> + @enderror + </div> <div class="form-group"> <label for="description">{{__('Description')}} <i data-toggle="popover" data-trigger="hover" @@ -152,6 +163,35 @@ class="form-control @error('disk') is-invalid @enderror" </div> @enderror </div> + + <div class="form-group"> + <label for="billing_period">{{__('Billing Period')}} <i + data-toggle="popover" data-trigger="hover" + data-content="{{__('Period when the user will be charged for the given price')}}" + class="fas fa-info-circle"></i></label> + + <select id="billing_period" style="width:100%" class="custom-select" name="billing_period" required + autocomplete="off" @error('billing_period') is-invalid @enderror> + <option value="hourly" @if ($product->billing_period == 'hourly') selected + @endif> + {{__('Hourly')}} + </option> + <option value="daily" @if ($product->billing_period == 'daily') selected + @endif> + {{__('Daily')}} + </option> + <option value="monthly" @if ($product->billing_period == 'monthly') selected + @endif> + {{__('Monthly')}} + </option> + </select> + @error('billing_period') + <div class="invalid-feedback"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group"> <label for="minimum_credits">{{__('Minimum')}} {{ CREDITS_DISPLAY_NAME }} <i data-toggle="popover" data-trigger="hover" @@ -202,18 +242,6 @@ class="form-control @error('backups') is-invalid @enderror" </div> @enderror </div> - <div class="form-group"> - <label for="allocations">{{__('Allocations')}}</label> - <input value="{{ $product->allocations }}" id="allocations" - name="allocations" type="number" - class="form-control @error('allocations') is-invalid @enderror" - required="required"> - @error('allocations') - <div class="invalid-feedback"> - {{ $message }} - </div> - @enderror - </div> </div> </div> diff --git a/resources/views/admin/products/index.blade.php b/resources/views/admin/products/index.blade.php index b7c241b3e..25cea9a09 100644 --- a/resources/views/admin/products/index.blade.php +++ b/resources/views/admin/products/index.blade.php @@ -44,6 +44,7 @@ class="fas fa-plus mr-1"></i>{{__('Create new')}}</a> <th>{{__('Active')}}</th> <th>{{__('Name')}}</th> <th>{{__('Price')}}</th> + <th>{{__('Billing period')}}</th> <th>{{__('Memory')}}</th> <th>{{__('Cpu')}}</th> <th>{{__('Swap')}}</th> @@ -91,6 +92,7 @@ function submitResult() { {data: "disabled"}, {data: "name"}, {data: "price"}, + {data: "billing_period"}, {data: "memory"}, {data: "cpu"}, {data: "swap"}, From 5c37dbcc51e3103fddb0f8896aaf77c19a56b35a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 12:36:01 +0200 Subject: [PATCH 009/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20billing=20p?= =?UTF-8?q?eriod=20to=20server=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/create.blade.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/views/servers/create.blade.php b/resources/views/servers/create.blade.php index 775ed5ac9..26f55848f 100644 --- a/resources/views/servers/create.blade.php +++ b/resources/views/servers/create.blade.php @@ -201,6 +201,12 @@ class="custom-select"> ({{ __('ports') }})</span> <span class="d-inline-block" x-text="product.allocations"></span> </li> + <li class="d-flex justify-content-between"> + <span class="d-inline-block"><i class="fas fa-clock"></i> + {{ __('Billing Period') }}</span> + + <span class="d-inline-block" x-text="product.billing_period"></span> + </li> </ul> </div> <div class="mt-2 mb-2"> @@ -210,8 +216,7 @@ class="custom-select"> </div> <div class="mt-auto border rounded border-secondary"> <div class="d-flex justify-content-between p-2"> - <span class="d-inline-block mr-4"> - {{ __('Price') }}: + <span class="d-inline-block mr-4" x-text="'{{ __('Price') }}' + ' (' + product.billing_period + ')'"> </span> <span class="d-inline-block" x-text="product.price + ' {{ CREDITS_DISPLAY_NAME }}'"></span> From 9e8bd0a2d3097b68cd9780d04971f0603e21982a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 14:54:06 +0200 Subject: [PATCH 010/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20weekly=20to?= =?UTF-8?q?=20billing=5Fperiod=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Admin/ProductController.php | 4 ++-- resources/views/admin/products/create.blade.php | 4 ++++ resources/views/admin/products/edit.blade.php | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index d86f7873f..772abc67a 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -73,7 +73,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,monthly", + "billing_period" => "required|in:hourly,daily,weekly,monthly", ]); $disabled = !is_null($request->input('disabled')); @@ -140,7 +140,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,monthly", + "billing_period" => "required|in:hourly,daily,weekly,monthly", ]); $disabled = !is_null($request->input('disabled')); diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index 2470bb175..0b9389a45 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -177,6 +177,10 @@ class="fas fa-info-circle"></i></label> <option value="daily" @if ($product->billing_period == 'daily') selected @endif> {{__('Daily')}} + </option> + <option value="weekly" @if ($product->billing_period == 'weekly') selected + @endif> + {{__('Weekly')}} </option> <option value="monthly" @if ($product->billing_period == 'monthly') selected @endif> diff --git a/resources/views/admin/products/edit.blade.php b/resources/views/admin/products/edit.blade.php index 651e44923..3294b1657 100644 --- a/resources/views/admin/products/edit.blade.php +++ b/resources/views/admin/products/edit.blade.php @@ -179,6 +179,10 @@ class="fas fa-info-circle"></i></label> <option value="daily" @if ($product->billing_period == 'daily') selected @endif> {{__('Daily')}} + </option> + <option value="weekly" @if ($product->billing_period == 'weekly') selected + @endif> + {{__('Weekly')}} </option> <option value="monthly" @if ($product->billing_period == 'monthly') selected @endif> From fb23f43f884283d66c401f2bb8d525475fd765e3 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 14:54:42 +0200 Subject: [PATCH 011/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20ChargeServe?= =?UTF-8?q?rs=20command=20&=20updated=20laravel=20schedule=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 122 +++++++++++++++++++++++++ app/Console/Kernel.php | 5 +- 2 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 app/Console/Commands/ChargeServers.php diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php new file mode 100644 index 000000000..678ab4b70 --- /dev/null +++ b/app/Console/Commands/ChargeServers.php @@ -0,0 +1,122 @@ +<?php + +namespace App\Console\Commands; + +use App\Models\Server; +use App\Notifications\ServersSuspendedNotification; +use Illuminate\Console\Command; + +class ChargeServers extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'servers:charge'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Charge all users with severs that are due to be charged'; + + /** + * A list of users that have to be notified + * @var array + */ + protected $usersToNotify = []; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + Server::whereNull('suspended')->with('users', 'products')->chunk(10, function ($servers) { + /** @var Server $server */ + foreach ($servers as $server) { + /** @var Product $product */ + $product = $server->product; + /** @var User $user */ + $user = $server->user; + + $billing_period = $product->billing_period; + + // check if server is due to be charged by comparing its last_billed date with the current date and the billing period + $newBillingDate = null; + switch($billing_period) { + case 'monthly': + $newBillingDate = $server->last_billed->addMonth(); + break; + case 'weekly': + $newBillingDate = $server->last_billed->addYear(); + break; + case 'daily': + $newBillingDate = $server->last_billed->addDay(); + break; + default: + $newBillingDate = $server->last_billed->addHour(); + break; + }; + if (!($newBillingDate <= now())) return; + + // check if user has enough credits to charge the server + if ($user->credits < $product->price) { + try { + #suspend server + $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); + $server->suspend(); + + #add user to notify list + if (!in_array($user, $this->usersToNotify)) { + array_push($this->usersToNotify, $user); + } + } catch (\Exception $exception) { + $this->error($exception->getMessage()); + } + return; + } + + // charge credits to user + $this->line("<fg=blue>{$user->name}</> Current credits: <fg=green>{$user->credits}</> Credits to be removed: <fg=red>{$product->price}</>"); + $user->decrement('credits', $product->price); + + // update server last_billed date + $server->last_billed = $newBillingDate; + } + + return $this->notifyUsers(); + }); + } + + /** + * @return bool + */ + public function notifyUsers() + { + if (!empty($this->usersToNotify)) { + /** @var User $user */ + foreach ($this->usersToNotify as $user) { + $this->line("<fg=yellow>Notified user:</> <fg=blue>{$user->name}</>"); + $user->notify(new ServersSuspendedNotification()); + } + } + + #reset array + $this->usersToNotify = array(); + return true; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index fe25d44cc..9a56bf909 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -14,7 +14,8 @@ class Kernel extends ConsoleKernel * @var array */ protected $commands = [ - // + Commands\ChargeCreditsCommand::class, + Commands\ChargeServers::class, ]; /** @@ -25,7 +26,7 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command('credits:charge')->hourly(); + $schedule->command('servers:charge')->everyMinute(); //log cronjob activity $schedule->call(function () { From 6c9334320004071d0e935bf08a8af88cca09c537 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 15:32:42 +0200 Subject: [PATCH 012/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20decimal?= =?UTF-8?q?=20input=20steps=20to=20number=20inputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/admin/products/create.blade.php | 1 + resources/views/admin/products/edit.blade.php | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index 0b9389a45..4f79bb20c 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -66,6 +66,7 @@ class="form-control @error('name') is-invalid @enderror" <label for="price">{{__('Price in')}} {{CREDITS_DISPLAY_NAME}}</label> <input value="{{$product->price ?? old('price')}}" id="price" name="price" type="number" + step="0.0001" class="form-control @error('price') is-invalid @enderror" required="required"> @error('price') diff --git a/resources/views/admin/products/edit.blade.php b/resources/views/admin/products/edit.blade.php index 3294b1657..cedbc72c8 100644 --- a/resources/views/admin/products/edit.blade.php +++ b/resources/views/admin/products/edit.blade.php @@ -75,8 +75,10 @@ class="form-control @error('name') is-invalid @enderror" </div> <div class="form-group"> - <label for="price">{{__('Price in')}} {{ CREDITS_DISPLAY_NAME }}</label> - <input value="{{ $product->price }}" id="price" name="price" type="number" + <label for="price">{{__('Price in')}} {{CREDITS_DISPLAY_NAME}}</label> + <input value="{{$product->price}}" id="price" name="price" + type="number" + step="0.0001" class="form-control @error('price') is-invalid @enderror" required="required"> @error('price') From d7c0c26f35ca6b494e8fd0c6175cce399d9d63e3 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 15:33:44 +0200 Subject: [PATCH 013/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Renamed=20Migrati?= =?UTF-8?q?on=20&=20changed=20precision=20on=20decimals=20to=20more=20reas?= =?UTF-8?q?onable=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2022_06_16_092704_add_billing_period_to_products.php | 5 +++++ ...hp => 2022_06_16_094402_update_user_credits_datatype.php} | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) rename database/migrations/{2022_06_16_094402_update_user_price_datatype.php => 2022_06_16_094402_update_user_credits_datatype.php} (82%) diff --git a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php index 708e6e5e4..ceed9362f 100644 --- a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php +++ b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php @@ -16,6 +16,9 @@ public function up() { Schema::table('products', function (Blueprint $table) { $table->string('billing_period')->default("hourly"); + $table->decimal('price', 15, 4)->change(); + $table->decimal('minimum_credits', 15, 4)->change(); + }); DB::statement('UPDATE products SET billing_period="hourly"'); @@ -38,6 +41,8 @@ public function down() { Schema::table('products', function (Blueprint $table) { $table->dropColumn('billing_period'); + $table->decimal('price', 10, 0)->change(); + $table->float('minimum_credits')->change(); }); } } diff --git a/database/migrations/2022_06_16_094402_update_user_price_datatype.php b/database/migrations/2022_06_16_094402_update_user_credits_datatype.php similarity index 82% rename from database/migrations/2022_06_16_094402_update_user_price_datatype.php rename to database/migrations/2022_06_16_094402_update_user_credits_datatype.php index 9b4abb9e7..ed5922e86 100644 --- a/database/migrations/2022_06_16_094402_update_user_price_datatype.php +++ b/database/migrations/2022_06_16_094402_update_user_credits_datatype.php @@ -4,7 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class UpdateUserPriceDatatype extends Migration +class UpdateUserCreditsDatatype extends Migration { /** * Run the migrations. @@ -14,7 +14,7 @@ class UpdateUserPriceDatatype extends Migration public function up() { Schema::table('users', function (Blueprint $table) { - $table->decimal('credits', 20, 10)->default(0)->change(); + $table->decimal('credits', 15, 4)->default(0)->change(); }); } @@ -27,6 +27,7 @@ public function down() { Schema::table('users', function (Blueprint $table) { $table->unsignedFloat('credits')->default(250)->change(); + }); } } From a26f6dd137298e7ed9dbf19624e06ee140c9b961 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 16:15:57 +0200 Subject: [PATCH 014/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20last=5Fbill?= =?UTF-8?q?ed=20to=20server=20model=20&=20always=20charge=20first=20&=20fi?= =?UTF-8?q?xed=20Charge=20Server=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 24 +++++++++++++++-------- app/Http/Controllers/ServerController.php | 12 ++++++------ app/Models/Server.php | 1 + 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 678ab4b70..877cb8a5a 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -4,7 +4,9 @@ use App\Models\Server; use App\Notifications\ServersSuspendedNotification; +use Carbon\Carbon; use Illuminate\Console\Command; +use Illuminate\Support\Facades\DB; class ChargeServers extends Command { @@ -45,7 +47,7 @@ public function __construct() */ public function handle() { - Server::whereNull('suspended')->with('users', 'products')->chunk(10, function ($servers) { + Server::whereNull('suspended')->with('user', 'product')->chunk(10, function ($servers) { /** @var Server $server */ foreach ($servers as $server) { /** @var Product $product */ @@ -55,23 +57,29 @@ public function handle() $billing_period = $product->billing_period; + // check if server is due to be charged by comparing its last_billed date with the current date and the billing period $newBillingDate = null; switch($billing_period) { case 'monthly': - $newBillingDate = $server->last_billed->addMonth(); + $newBillingDate = Carbon::parse($server->last_billed)->addMonth(); break; case 'weekly': - $newBillingDate = $server->last_billed->addYear(); + $newBillingDate = Carbon::parse($server->last_billed)->addYear(); break; case 'daily': - $newBillingDate = $server->last_billed->addDay(); + $newBillingDate = Carbon::parse($server->last_billed)->addDay(); break; + case 'hourly': + $newBillingDate = Carbon::parse($server->last_billed)->addHour(); default: - $newBillingDate = $server->last_billed->addHour(); + $newBillingDate = Carbon::parse($server->last_billed)->addHour(); break; }; - if (!($newBillingDate <= now())) return; + + if (!($newBillingDate->isPast())) { + continue; + } // check if user has enough credits to charge the server if ($user->credits < $product->price) { @@ -94,8 +102,8 @@ public function handle() $this->line("<fg=blue>{$user->name}</> Current credits: <fg=green>{$user->credits}</> Credits to be removed: <fg=red>{$product->price}</>"); $user->decrement('credits', $product->price); - // update server last_billed date - $server->last_billed = $newBillingDate; + // update server last_billed date in db + DB::table('servers')->where('id', $server->id)->update(['last_billed' => $newBillingDate]); } return $this->notifyUsers(); diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index c1c0a33ab..45d72569c 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -11,6 +11,7 @@ use App\Models\Server; use App\Models\Settings; use App\Notifications\ServerCreationError; +use Carbon\Carbon; use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Client\Response; @@ -151,6 +152,7 @@ public function store(Request $request) $server = $request->user()->servers()->create([ 'name' => $request->input('name'), 'product_id' => $request->input('product'), + 'last_billed' => Carbon::now()->toDateTimeString(), ]); //get free allocation ID @@ -165,14 +167,12 @@ public function store(Request $request) //update server with pterodactyl_id $server->update([ 'pterodactyl_id' => $serverAttributes['id'], - 'identifier' => $serverAttributes['identifier'] + 'identifier' => $serverAttributes['identifier'], + ]); - if (config('SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR', 'true') == 'true') { - if ($request->user()->credits >= $server->product->getHourlyPrice()) { - $request->user()->decrement('credits', $server->product->getHourlyPrice()); - } - } + // Charge first billing cycle + $request->user()->decrement('credits', $server->product->price); return redirect()->route('servers.index')->with('success', __('Server created')); } diff --git a/app/Models/Server.php b/app/Models/Server.php index 34c992ba7..82770a30c 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -47,6 +47,7 @@ class Server extends Model "identifier", "product_id", "pterodactyl_id", + "last_billed" ]; /** From cad41ffb0a8359e1be0990c2e2b96c42334424a0 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 16:18:27 +0200 Subject: [PATCH 015/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Update=20last=5Fbil?= =?UTF-8?q?led=20to=20current=20time=20on=20unsuspend=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Server.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Models/Server.php b/app/Models/Server.php index 82770a30c..fa6d17caf 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Classes\Pterodactyl; +use Carbon\Carbon; use Exception; use GuzzleHttp\Promise\PromiseInterface; use Hidehalo\Nanoid\Client; @@ -124,10 +125,12 @@ public function unSuspend() if ($response->successful()) { $this->update([ - 'suspended' => null + 'suspended' => null, + 'last_billed' => Carbon::now()->toDateTimeString(), ]); } + return $this; } From 570b0b014f07fc3890ce0341b64583c56b942a10 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 19:03:59 +0200 Subject: [PATCH 016/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20ChargeSe?= =?UTF-8?q?rver=20Command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 877cb8a5a..769909c32 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -65,7 +65,7 @@ public function handle() $newBillingDate = Carbon::parse($server->last_billed)->addMonth(); break; case 'weekly': - $newBillingDate = Carbon::parse($server->last_billed)->addYear(); + $newBillingDate = Carbon::parse($server->last_billed)->addWeek(); break; case 'daily': $newBillingDate = Carbon::parse($server->last_billed)->addDay(); From 609041a96799e42705ef1aa12244951171601e12 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Sat, 18 Jun 2022 23:41:40 +0200 Subject: [PATCH 017/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20different?= =?UTF-8?q?=20billing=20periods=20to=20servers=20overview=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 29 ++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 72187653a..517c3f1ea 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -127,22 +127,21 @@ class="fas fa-info-circle"></i> ({{ CREDITS_DISPLAY_NAME }}) </span> </div> - <div class="col-8"> - <div class="row"> - <div class="col-6 text-center"> - <div class="text-muted">{{ __('per Hour') }}</div> - <span> - {{ number_format($server->product->getHourlyPrice(), 2, '.', '') }} - </span> + <div class="col-8 text-center"> + <div class="text-muted"> + @if($server->product->billing_period == 'monthly') + {{ __('per Month') }} + @elseif($server->product->billing_period == 'weekly') + {{ __('per Week') }} + @elseif($server->product->billing_period == 'daily') + {{ __('per Day') }} + @elseif($server->product->billing_period == 'hourly') + {{ __('per Hour') }} + @endif </div> - <div class="col-6 text-center"> - <div class="text-muted">{{ __('per Month') }} - </div> - <span> - {{ $server->product->getHourlyPrice() * 24 * 30 }} - </span> - </div> - </div> + <span> + {{ $server->product->price }} + </span> </div> </div> </div> From b9b860b863059c717fa714fb81e25439189f535a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Sat, 18 Jun 2022 23:43:58 +0200 Subject: [PATCH 018/514] =?UTF-8?q?fix:=20=F0=9F=94=A5=20Removed=20charge?= =?UTF-8?q?=20first=20hour=20at=20creation=20->=20not=20in=20use=20anymore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/admin/settings/tabs/system.blade.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/resources/views/admin/settings/tabs/system.blade.php b/resources/views/admin/settings/tabs/system.blade.php index aece6bee0..3fd3e67ed 100644 --- a/resources/views/admin/settings/tabs/system.blade.php +++ b/resources/views/admin/settings/tabs/system.blade.php @@ -26,22 +26,6 @@ class="fas fa-info-circle"></i> </div> </div> - <div class="custom-control mb-3 p-0"> - <div class="col m-0 p-0 d-flex justify-content-between align-items-center"> - <div> - <input value="true" id="server-create-charge-first-hour" - name="server-create-charge-first-hour" - {{ config('SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR') == 'true' ? 'checked' : '' }} - type="checkbox"> - <label for="server-create-charge-first-hour">{{ __('Charge first hour at creation') }} - </label> - </div> - <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('Charges the first hour worth of credits upon creating a server.') }}" - class="fas fa-info-circle"></i> - </div> - </div> - <div class="custom-control mb-3 p-0"> <label for="credits-display-name">{{ __('Credits Display Name') }}</label> <input x-model="credits-display-name" id="credits-display-name" name="credits-display-name" From b6775f768d6948c5a1d9ac6fc138d95ec1cf4b3e Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Sat, 18 Jun 2022 23:55:43 +0200 Subject: [PATCH 019/514] =?UTF-8?q?docs:=20=F0=9F=93=9D=20Added=20Addon=20?= =?UTF-8?q?Docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Addon-notes.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Addon-notes.md diff --git a/Addon-notes.md b/Addon-notes.md new file mode 100644 index 000000000..3c335a7f6 --- /dev/null +++ b/Addon-notes.md @@ -0,0 +1,3 @@ +Export diff files: + +cp -pv --parents $(git diff <commit> --name-only) "..\controllpanelgg-monthly-addon\files-git-diff\" From dfa50141fbd9dcd637cd87f7d4950d2397ace414 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Sun, 19 Jun 2022 00:13:54 +0200 Subject: [PATCH 020/514] =?UTF-8?q?docs:=20=F0=9F=93=9D=20Updated=20Export?= =?UTF-8?q?=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Addon-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Addon-notes.md b/Addon-notes.md index 3c335a7f6..baf40c6aa 100644 --- a/Addon-notes.md +++ b/Addon-notes.md @@ -1,3 +1,3 @@ Export diff files: -cp -pv --parents $(git diff <commit> --name-only) "..\controllpanelgg-monthly-addon\files-git-diff\" +git diff -r --no-commit-id --name-only --diff-filter=ACMR <commit> | tar -czf file.tgz -T - From 43119e22a5fc093c12a0f2ab46ee63d645c27fb6 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Wed, 22 Jun 2022 12:01:47 +0200 Subject: [PATCH 021/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20No=20pro?= =?UTF-8?q?duct=20available=20at=20creation=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/admin/products/create.blade.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index 4f79bb20c..747ee1a6c 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -171,20 +171,16 @@ class="fas fa-info-circle"></i></label> <select id="billing_period" style="width:100%" class="custom-select" name="billing_period" required autocomplete="off" @error('billing_period') is-invalid @enderror> - <option value="hourly" @if ($product->billing_period == 'hourly') selected - @endif> + <option value="hourly" selected> {{__('Hourly')}} </option> - <option value="daily" @if ($product->billing_period == 'daily') selected - @endif> + <option value="daily"> {{__('Daily')}} </option> - <option value="weekly" @if ($product->billing_period == 'weekly') selected - @endif> + <option value="weekly"> {{__('Weekly')}} </option> - <option value="monthly" @if ($product->billing_period == 'monthly') selected - @endif> + <option value="monthly"> {{__('Monthly')}} </option> </select> From 690e0e7e1239d26f956c8e97f2099f313d77034a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 01:50:45 +0200 Subject: [PATCH 022/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20migrations?= =?UTF-8?q?=20undo=20last=20patch=20db=20update=20&=20add=20cancelation=20?= =?UTF-8?q?of=20servers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...34527_add_cancelation_to_servers_table.php | 32 +++++++++++++++++++ ...022_07_21_234818_undo_decimal_in_price.php | 32 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php create mode 100644 database/migrations/2022_07_21_234818_undo_decimal_in_price.php diff --git a/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php b/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php new file mode 100644 index 000000000..b9f758391 --- /dev/null +++ b/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class AddCancelationToServersTable extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('servers', function (Blueprint $table) { + $table->boolean('canceled')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('canceled'); + }); + } +} diff --git a/database/migrations/2022_07_21_234818_undo_decimal_in_price.php b/database/migrations/2022_07_21_234818_undo_decimal_in_price.php new file mode 100644 index 000000000..cf4abb69b --- /dev/null +++ b/database/migrations/2022_07_21_234818_undo_decimal_in_price.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class UndoDecimalInPrice extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('products', function (Blueprint $table) { + $table->decimal('price', 15, 4)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('products', function (Blueprint $table) { + $table->decimal('price',['11','2'])->change(); + }); + } +} From e207c0c5509bf1b2bea3650e3a7c5b26cbf2b466 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 02:58:08 +0200 Subject: [PATCH 023/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Cancel=20Bu?= =?UTF-8?q?tton=20&=20Next=20Billing=20Cycle=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 116 ++++++++++++++++++------ 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 517c3f1ea..9c9d81b06 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -40,7 +40,6 @@ class="fa fa-plus mr-2"></i> <div class="row d-flex flex-row justify-content-center justify-content-md-start"> @foreach ($servers as $server) - <div class="col-xl-3 col-lg-5 col-md-6 col-sm-6 col-xs-12 card pr-0 pl-0 ml-sm-2 mr-sm-3" style="max-width: 350px"> <div class="card-header"> @@ -107,7 +106,7 @@ class="fas fa-info-circle"></i> <span>{{ $server->egg }}</span> </div> </div> - <div class="row mb-4"> + <div class="row mb-2"> <div class="col-5 "> {{ __('Resource plan') }}: </div> @@ -115,11 +114,43 @@ class="fas fa-info-circle"></i> <span>{{ $server->product->name }} </span> <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/>" + data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/> {{ __('Billing Period') }}: {{$server->product->billing_period}}" class="fas fa-info-circle"></i> </div> + </div> + <div class="row mb-4 "> + <div class="col-5 "> + {{ __('Next Billing Cycle') }}: + </div> + <div class="col-7 d-flex text-wrap align-items-center"> + <span> + @switch($server->product->billing_period) + @case('monthly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonth()->toDayDateTimeString(); }} + @break + @case('weekly') + {{ \Carbon\Carbon::parse($server->last_billed)->addWeek()->toDayDateTimeString(); }} + @break + @case('daily') + {{ \Carbon\Carbon::parse($server->last_billed)->addDay()->toDayDateTimeString(); }} + @break + @case('hourly') + {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} + @break + @case('half-yearly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} + @break + @case('yearly') + {{ \Carbon\Carbon::parse($server->last_billed)->addYear()->toDayDateTimeString(); }} + @break + @default + {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} + @endswitch + </span> + </div> </div> + <div class="row mb-2"> <div class="col-4"> {{ __('Price') }}: @@ -147,17 +178,22 @@ class="fas fa-info-circle"></i> </div> </div> - <div class="card-footer d-flex align-items-center justify-content-between"> + <div class="card-footer text-center"> <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" target="__blank" - class="btn btn-info mx-3 w-100 align-items-center justify-content-center d-flex"> - <i class="fas fa-tools mr-2"></i> - <span>{{ __('Manage') }}</span> + class="btn btn-info text-center float-left ml-2" + data-toggle="tooltip" data-placement="bottom" title="Manage Server"> + <i class="fas fa-tools mx-4"></i> </a> - <button onclick="confirmSubmit('{{ $server->id }}', handleServerDelete);" target="__blank" - class="btn btn-danger mx-3 w-100 align-items-center justify-content-center d-flex"> - <i class="fas fa-trash mr-2"></i> - <span>{{ __('Delete') }}</span> + <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" + class="btn btn-warning text-center" + data-toggle="tooltip" data-placement="bottom" title="Cancel Server"> + <i class="fas fa-ban mx-4"></i> + </button> + <button onclick="handleServerDelete('{{ $server->id }}');" target="__blank" + class="btn btn-danger text-center float-right mr-2" + data-toggle="tooltip" data-placement="bottom" title="Delete Server"> + <i class="fas fa-trash mx-4"></i> </button> </div> </div> @@ -169,40 +205,66 @@ class="btn btn-danger mx-3 w-100 align-items-center justify-content-center d-fle <!-- END CONTENT --> <script> - const confirmSubmit = (serverId, handleServerDelete) => { - // Confirm delete submit with sweetalert + const handleServerCancel = (serverId) => { + // Handle server cancel with sweetalert Swal.fire({ - title: "{{ __('Are you sure?') }}", - text: "{{ __('This is an irreversible action, all files of this server will be removed.') }}", + title: "{{ __('Cancel Server?') }}", + text: "{{ __('This will cancel your current server to the next billing period. It will get suspended when the current period runs out.') }}", icon: 'warning', confirmButtonColor: '#d9534f', showCancelButton: true, - confirmButtonText: "{{ __('Yes, delete it!') }}", - cancelButtonText: "{{ __('No, cancel!') }}", + confirmButtonText: "{{ __('Yes, cancel it!') }}", + cancelButtonText: "{{ __('No, abort!') }}", reverseButtons: true }).then((result) => { if (result.value) { - handleServerDelete(serverId); + // Delete server + fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + window.location.reload(); + }); return } - Swal.fire("{{ __('Canceled ...') }}", `{{ __('Deletion has been canceled.') }}`, 'info'); - }); + }) } const handleServerDelete = (serverId) => { - // Delete server - fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { - method: 'DELETE', - headers: { - 'X-CSRF-TOKEN': '{{ csrf_token() }}' + Swal.fire({ + title: "{{ __('Delete Server?') }}", + text: "{{ __('This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.') }}", + icon: 'warning', + confirmButtonColor: '#d9534f', + showCancelButton: true, + confirmButtonText: "{{ __('Yes, delete it!') }}", + cancelButtonText: "{{ __('No, abort!') }}", + reverseButtons: true + }).then((result) => { + if (result.value) { + // Delete server + fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + window.location.reload(); + }); + return } - }).then(() => { - window.location.reload(); }); + } document.addEventListener('DOMContentLoaded', () => { $('[data-toggle="popover"]').popover(); }); + + $(function () { + $('[data-toggle="tooltip"]').tooltip() + }) </script> @endsection From 32384044ea984b699de7d451c5440950f5b9e20a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 03:51:05 +0200 Subject: [PATCH 024/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2022_07_21_234527_add_cancelation_to_servers_table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php b/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php index b9f758391..15fafb168 100644 --- a/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php +++ b/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php @@ -14,7 +14,7 @@ class AddCancelationToServersTable extends Migration public function up() { Schema::table('servers', function (Blueprint $table) { - $table->boolean('canceled')->default(false); + $table->dateTime('cancelled')->nullable(); }); } @@ -26,7 +26,7 @@ public function up() public function down() { Schema::table('servers', function (Blueprint $table) { - $table->dropColumn('canceled'); + $table->dropColumn('cancelled'); }); } } From 78a6787607b9229986585c6089dae99a6049f768 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 03:52:49 +0200 Subject: [PATCH 025/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Server=20Ca?= =?UTF-8?q?ncelation=20route=20method=20and=20charging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 8 ++++---- .../Controllers/Admin/ServerController.php | 18 ++++++++++++++++++ app/Http/Controllers/ServerController.php | 16 +++++++++++++++- app/Models/Server.php | 3 ++- resources/views/servers/index.blade.php | 4 ++-- routes/web.php | 2 ++ 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 769909c32..2a5d8a5ad 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -81,14 +81,14 @@ public function handle() continue; } - // check if user has enough credits to charge the server - if ($user->credits < $product->price) { + // check if the server is canceled or if user has enough credits to charge the server or + if ( $server->cancelled || $user->credits < $product->price) { try { - #suspend server + // suspend server $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); $server->suspend(); - #add user to notify list + // add user to notify list if (!in_array($user, $this->usersToNotify)) { array_push($this->usersToNotify, $user); } diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php index 5ebc29bce..594d7a0f6 100644 --- a/app/Http/Controllers/Admin/ServerController.php +++ b/app/Http/Controllers/Admin/ServerController.php @@ -108,6 +108,24 @@ public function destroy(Server $server) } } + /** + * Cancel the Server billing cycle. + * + * @param Server $server + * @return RedirectResponse|Response + */ + public function cancel (Server $server) + { + try { + error_log($server->update([ + 'cancelled' => now(), + ])); + return redirect()->route('servers.index')->with('success', __('Server cancelled')); + } catch (Exception $e) { + return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to cancel the server"') . $e->getMessage() . '"'); + } + } + /** * @param Server $server * @return RedirectResponse diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 45d72569c..ee9c47b76 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -210,7 +210,21 @@ public function destroy(Server $server) $server->delete(); return redirect()->route('servers.index')->with('success', __('Server removed')); } catch (Exception $e) { - return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to remove a resource "') . $e->getMessage() . '"'); + return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to delete the server"') . $e->getMessage() . '"'); + } + } + + /** Cancel Server */ + public function cancel (Server $server) + { + try { + error_log($server->update([ + 'cancelled' => now(), + ])); + + return redirect()->route('servers.index')->with('success', __('Server cancelled')); + } catch (Exception $e) { + return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to cancel the server"') . $e->getMessage() . '"'); } } } diff --git a/app/Models/Server.php b/app/Models/Server.php index fa6d17caf..5f87d488c 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -48,7 +48,8 @@ class Server extends Model "identifier", "product_id", "pterodactyl_id", - "last_billed" + "last_billed", + "cancelled" ]; /** diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 9c9d81b06..e96b46b12 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -219,8 +219,8 @@ class="btn btn-danger text-center float-right mr-2" }).then((result) => { if (result.value) { // Delete server - fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { - method: 'DELETE', + fetch("{{ route('servers.cancel', '') }}" + '/' + serverId, { + method: 'PATCH', headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' } diff --git a/routes/web.php b/routes/web.php index 168549f74..8755eb324 100644 --- a/routes/web.php +++ b/routes/web.php @@ -60,6 +60,7 @@ #normal routes Route::get('notifications/readAll',[NotificationController::class,'readAll'])->name('notifications.readAll'); Route::resource('notifications', NotificationController::class); + Route::patch('/servers/cancel/{server}', [ServerController::class, 'cancel'])->name('servers.cancel'); Route::resource('servers', ServerController::class); Route::resource('profile', ProfileController::class); Route::resource('store', StoreController::class); @@ -112,6 +113,7 @@ #servers Route::get('servers/datatable', [AdminServerController::class, 'datatable'])->name('servers.datatable'); Route::post('servers/togglesuspend/{server}', [AdminServerController::class, 'toggleSuspended'])->name('servers.togglesuspend'); + Route::patch('/servers/cancel/{server}', [AdminServerController::class, 'cancel'])->name('servers.cancel'); Route::resource('servers', AdminServerController::class); #products From 08208cab727a73e75f51a33aa5ba7868606b953d Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:00:23 +0200 Subject: [PATCH 026/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20yearly=20an?= =?UTF-8?q?d=20half-yearly=20billing=20periods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 6 ++++++ app/Http/Controllers/Admin/ProductController.php | 4 ++-- resources/views/admin/products/create.blade.php | 6 ++++++ resources/views/admin/products/edit.blade.php | 8 ++++++++ resources/views/servers/index.blade.php | 4 ++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 2a5d8a5ad..45f18c2b1 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -61,6 +61,12 @@ public function handle() // check if server is due to be charged by comparing its last_billed date with the current date and the billing period $newBillingDate = null; switch($billing_period) { + case 'yearly': + $newBillingDate = Carbon::parse($server->last_billed)->addYear(); + break; + case 'half-yearly': + $newBillingDate = Carbon::parse($server->last_billed)->addMonths(6); + break; case 'monthly': $newBillingDate = Carbon::parse($server->last_billed)->addMonth(); break; diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 772abc67a..9fb518015 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -73,7 +73,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly", + "billing_period" => "required|in:hourly,daily,weekly,monthly,half-yearly,yearly", ]); $disabled = !is_null($request->input('disabled')); @@ -140,7 +140,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly", + "billing_period" => "required|in:hourly,daily,weekly,monthly,half-yearly,yearly", ]); $disabled = !is_null($request->input('disabled')); diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index 8e0465fc1..578fa9104 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -183,6 +183,12 @@ class="fas fa-info-circle"></i></label> <option value="monthly"> {{__('Monthly')}} </option> + <option value="half-yearly"> + {{__('Half Yearly')}} + </option> + <option value="yearly"> + {{__('Yearly')}} + </option> </select> @error('billing_period') <div class="invalid-feedback"> diff --git a/resources/views/admin/products/edit.blade.php b/resources/views/admin/products/edit.blade.php index cedbc72c8..3401577b5 100644 --- a/resources/views/admin/products/edit.blade.php +++ b/resources/views/admin/products/edit.blade.php @@ -190,6 +190,14 @@ class="fas fa-info-circle"></i></label> @endif> {{__('Monthly')}} </option> + <option value="half-yearly" @if ($product->billing_period == 'half-yearly') selected + @endif> + {{__('Half Yearly')}} + </option> + <option value="yearly" @if ($product->billing_period == 'yearly') selected + @endif> + {{__('Yearly')}} + </option> </select> @error('billing_period') <div class="invalid-feedback"> diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index e96b46b12..1d0edd657 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -162,6 +162,10 @@ class="fas fa-info-circle"></i> <div class="text-muted"> @if($server->product->billing_period == 'monthly') {{ __('per Month') }} + @elseif($server->product->billing_period == 'half-yearly') + {{ __('per 6 Months') }} + @elseif($server->product->billing_period == 'yearly') + {{ __('per Year') }} @elseif($server->product->billing_period == 'weekly') {{ __('per Week') }} @elseif($server->product->billing_period == 'daily') From 7369e8a643e5016459e5fd5be6e1e8682b9b44a1 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:09:34 +0200 Subject: [PATCH 027/514] =?UTF-8?q?refactor:=20=F0=9F=9A=9A=20Rename=20Yea?= =?UTF-8?q?rly=20->=20Annually?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 4 +- .../Controllers/Admin/ProductController.php | 4 +- .../views/admin/products/create.blade.php | 8 +-- resources/views/admin/products/edit.blade.php | 8 +-- resources/views/servers/index.blade.php | 62 +++++++++++-------- 5 files changed, 47 insertions(+), 39 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 45f18c2b1..dac01f0f8 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -61,10 +61,10 @@ public function handle() // check if server is due to be charged by comparing its last_billed date with the current date and the billing period $newBillingDate = null; switch($billing_period) { - case 'yearly': + case 'annually': $newBillingDate = Carbon::parse($server->last_billed)->addYear(); break; - case 'half-yearly': + case 'half-annually': $newBillingDate = Carbon::parse($server->last_billed)->addMonths(6); break; case 'monthly': diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 9fb518015..b24c5cc6b 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -73,7 +73,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly,half-yearly,yearly", + "billing_period" => "required|in:hourly,daily,weekly,monthly,half-annually,annually", ]); $disabled = !is_null($request->input('disabled')); @@ -140,7 +140,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly,half-yearly,yearly", + "billing_period" => "required|in:hourly,daily,weekly,monthly,half-annually,annually", ]); $disabled = !is_null($request->input('disabled')); diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index 578fa9104..f44f64f41 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -183,11 +183,11 @@ class="fas fa-info-circle"></i></label> <option value="monthly"> {{__('Monthly')}} </option> - <option value="half-yearly"> - {{__('Half Yearly')}} + <option value="half-annually"> + {{__('Half Annually')}} </option> - <option value="yearly"> - {{__('Yearly')}} + <option value="annually"> + {{__('Annually')}} </option> </select> @error('billing_period') diff --git a/resources/views/admin/products/edit.blade.php b/resources/views/admin/products/edit.blade.php index 3401577b5..9d3aec529 100644 --- a/resources/views/admin/products/edit.blade.php +++ b/resources/views/admin/products/edit.blade.php @@ -190,13 +190,13 @@ class="fas fa-info-circle"></i></label> @endif> {{__('Monthly')}} </option> - <option value="half-yearly" @if ($product->billing_period == 'half-yearly') selected + <option value="half-annually" @if ($product->billing_period == 'half-annually') selected @endif> - {{__('Half Yearly')}} + {{__('Half Annually')}} </option> - <option value="yearly" @if ($product->billing_period == 'yearly') selected + <option value="annually" @if ($product->billing_period == 'annually') selected @endif> - {{__('Yearly')}} + {{__('Annually')}} </option> </select> @error('billing_period') diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 1d0edd657..fdcf2d104 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -72,9 +72,13 @@ class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') } <div class="row mb-3"> <div class="col my-auto">{{ __('Status') }}:</div> <div class="col-7 my-auto"> - <i - class="fas {{ $server->isSuspended() ? 'text-danger' : 'text-success' }} fa-circle mr-2"></i> - {{ $server->isSuspended() ? 'Suspended' : 'Active' }} + @if($server->suspennded) + <span class="badge badge-danger">{{ __('Suspended') }}</span> + @elseif($server->cancelled) + <span class="badge badge-warning">{{ __('Cancelled') }}</span> + @else + <span class="badge badge-success">{{ __('Active') }}</span> + @endif </div> </div> <div class="row mb-2"> @@ -125,28 +129,32 @@ class="fas fa-info-circle"></i> </div> <div class="col-7 d-flex text-wrap align-items-center"> <span> - @switch($server->product->billing_period) - @case('monthly') - {{ \Carbon\Carbon::parse($server->last_billed)->addMonth()->toDayDateTimeString(); }} - @break - @case('weekly') - {{ \Carbon\Carbon::parse($server->last_billed)->addWeek()->toDayDateTimeString(); }} - @break - @case('daily') - {{ \Carbon\Carbon::parse($server->last_billed)->addDay()->toDayDateTimeString(); }} - @break - @case('hourly') - {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} - @break - @case('half-yearly') - {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} - @break - @case('yearly') - {{ \Carbon\Carbon::parse($server->last_billed)->addYear()->toDayDateTimeString(); }} - @break - @default - {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} - @endswitch + @if ($server->cancelled) + - + @else + @switch($server->product->billing_period) + @case('monthly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonth()->toDayDateTimeString(); }} + @break + @case('weekly') + {{ \Carbon\Carbon::parse($server->last_billed)->addWeek()->toDayDateTimeString(); }} + @break + @case('daily') + {{ \Carbon\Carbon::parse($server->last_billed)->addDay()->toDayDateTimeString(); }} + @break + @case('hourly') + {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} + @break + @case('half-annually') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} + @break + @case('annually') + {{ \Carbon\Carbon::parse($server->last_billed)->addYear()->toDayDateTimeString(); }} + @break + @default + {{ __('Unknown') }} + @endswitch + @endif </span> </div> </div> @@ -162,9 +170,9 @@ class="fas fa-info-circle"></i> <div class="text-muted"> @if($server->product->billing_period == 'monthly') {{ __('per Month') }} - @elseif($server->product->billing_period == 'half-yearly') + @elseif($server->product->billing_period == 'half-annually') {{ __('per 6 Months') }} - @elseif($server->product->billing_period == 'yearly') + @elseif($server->product->billing_period == 'annually') {{ __('per Year') }} @elseif($server->product->billing_period == 'weekly') {{ __('per Week') }} From dbbdfaa623f21e393bc30c54d49921ee3320a5c6 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:15:40 +0200 Subject: [PATCH 028/514] =?UTF-8?q?fix:=20=F0=9F=93=9D=20Undo=20Naming?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index ee9c47b76..167325887 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -210,7 +210,7 @@ public function destroy(Server $server) $server->delete(); return redirect()->route('servers.index')->with('success', __('Server removed')); } catch (Exception $e) { - return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to delete the server"') . $e->getMessage() . '"'); + return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to remove a resource"') . $e->getMessage() . '"'); } } From 737bf6e8e9564839dade4f47e1e7124c2217d518 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:25:23 +0200 Subject: [PATCH 029/514] =?UTF-8?q?chore:=20=F0=9F=8C=90=20Localization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/lang/de.json | 23 ++++++++++++++++++++++- resources/lang/en.json | 23 ++++++++++++++++++++++- resources/views/servers/index.blade.php | 6 +++--- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/resources/lang/de.json b/resources/lang/de.json index 364ae6078..811c0cea9 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -442,5 +442,26 @@ "pl": "Polnisch", "zh": "Chinesisch", "tr": "Türkisch", - "ru": "Russisch" + "ru": "Russisch", + "hourly": "Stündlich", + "monthly": "Monatlich", + "yearly": "Jährlich", + "daily": "Täglich", + "weekly": "Wöchentlich", + "half-annually": "Halbjährlich", + "annually": "Jährlich", + "Cancelled": "Gekündigt", + "An exception has occurred while trying to cancel the server": "Ein Fehler ist aufgetreten beim Versuch, den Server zu kündigen", + "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "Dies wird Ihren aktuellen Server zur nächsten Abrechnungsperiode kündigen. Er wird beim Ablauf der aktuellen Periode gesperrt.", + "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.": "Dies ist eine irreversibel Aktion, alle Dateien dieses Servers werden gelöscht. Keine Gelder werden zurückgezahlt. Wir empfehlen, den Server zu löschen, wenn er gesperrt ist.", + "Cancel Server?": "Server kündigen?", + "Delete Server?": "Server löschen?", + "Billing Period": "Abrechnungsperiode", + "Next Billing Cycle": "Nächste Abrechnungsperiode", + "Manage Server": "Server verwalten", + "Delete Server": "Server löschen", + "Cancel Server": "Server kündigen", + "Yes, cancel it!": "Ja, löschen!", + "No, abort!": "Abbrechen", + "Billing period": "Abrechnungsperiode" } diff --git a/resources/lang/en.json b/resources/lang/en.json index 37beb2a3d..6a865f29d 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -444,5 +444,26 @@ "pl": "Polish", "zh": "Chinese", "tr": "Turkish", - "ru": "Russian" + "ru": "Russian", + "hourly": "Hourly", + "monthly": "Monthly", + "yearly": "Yearly", + "daily": "Daily", + "weekly": "Weekly", + "half-annually": "Half-annually", + "annually": "Annually", + "Cancelled": "Cancelled", + "An exception has occurred while trying to cancel the server": "An exception has occurred while trying to cancel the server", + "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.", + "Cancel Server?": "Cancel Server?", + "Delete Server?": "Delete Server?", + "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.": "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.", + "Billing Period": "Billing Period", + "Next Billing Cycle": "Next Billing Cycle", + "Manage Server": "Manage Server", + "Delete Server": "Delete Server", + "Cancel Server": "Cancel Server", + "Yes, cancel it!": "Yes, cancel it!", + "No, abort!": "No, abort!", + "Billing period": "Billing period" } diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index fdcf2d104..d907a2780 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -194,17 +194,17 @@ class="fas fa-info-circle"></i> <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" target="__blank" class="btn btn-info text-center float-left ml-2" - data-toggle="tooltip" data-placement="bottom" title="Manage Server"> + data-toggle="tooltip" data-placement="bottom" title="{{ __('Manage Server') }}"> <i class="fas fa-tools mx-4"></i> </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" - data-toggle="tooltip" data-placement="bottom" title="Cancel Server"> + data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-4"></i> </button> <button onclick="handleServerDelete('{{ $server->id }}');" target="__blank" class="btn btn-danger text-center float-right mr-2" - data-toggle="tooltip" data-placement="bottom" title="Delete Server"> + data-toggle="tooltip" data-placement="bottom" title="{{ __('Delete Server') }}"> <i class="fas fa-trash mx-4"></i> </button> </div> From 23a890ecba73bcc9adbd9f53e110d1c83c27715f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:31:00 +0200 Subject: [PATCH 030/514] =?UTF-8?q?fix:=20=F0=9F=92=84=20Added=20hyphens?= =?UTF-8?q?=20at=20next=20billing=20cycle=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index d907a2780..df7bd1789 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -124,7 +124,7 @@ class="fas fa-info-circle"></i> </div> <div class="row mb-4 "> - <div class="col-5 "> + <div class="col-5 word-break" style="hyphens: auto"> {{ __('Next Billing Cycle') }}: </div> <div class="col-7 d-flex text-wrap align-items-center"> From 620a6b83e9878633618b28e7a2249c8c7144c56f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:33:54 +0200 Subject: [PATCH 031/514] =?UTF-8?q?style:=20=F0=9F=92=84=20Changed=20Next?= =?UTF-8?q?=20Billing=20Cycle=20behaviour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index df7bd1789..9b5a3ac9d 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -129,7 +129,7 @@ class="fas fa-info-circle"></i> </div> <div class="col-7 d-flex text-wrap align-items-center"> <span> - @if ($server->cancelled) + @if ($server->suspended) - @else @switch($server->product->billing_period) From abd8bc6b9c3a2ff9250a58fe525e78c0e8eb787a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:40:03 +0200 Subject: [PATCH 032/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Fiy=20suspended?= =?UTF-8?q?=20typo=20&=20localization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/lang/de.json | 1 + resources/lang/en.json | 1 + resources/views/servers/index.blade.php | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/lang/de.json b/resources/lang/de.json index 811c0cea9..184785c6b 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -450,6 +450,7 @@ "weekly": "Wöchentlich", "half-annually": "Halbjährlich", "annually": "Jährlich", + "Suspended": "Gesperrt", "Cancelled": "Gekündigt", "An exception has occurred while trying to cancel the server": "Ein Fehler ist aufgetreten beim Versuch, den Server zu kündigen", "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "Dies wird Ihren aktuellen Server zur nächsten Abrechnungsperiode kündigen. Er wird beim Ablauf der aktuellen Periode gesperrt.", diff --git a/resources/lang/en.json b/resources/lang/en.json index 6a865f29d..430bb7d29 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -452,6 +452,7 @@ "weekly": "Weekly", "half-annually": "Half-annually", "annually": "Annually", + "Suspended": "Suspended", "Cancelled": "Cancelled", "An exception has occurred while trying to cancel the server": "An exception has occurred while trying to cancel the server", "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.", diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 9b5a3ac9d..0fdac5a2f 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -72,7 +72,7 @@ class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') } <div class="row mb-3"> <div class="col my-auto">{{ __('Status') }}:</div> <div class="col-7 my-auto"> - @if($server->suspennded) + @if($server->suspended) <span class="badge badge-danger">{{ __('Suspended') }}</span> @elseif($server->cancelled) <span class="badge badge-warning">{{ __('Cancelled') }}</span> @@ -199,6 +199,7 @@ class="btn btn-info text-center float-left ml-2" </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" + disabled="{{ $server->suspended }}" data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-4"></i> </button> From f3856c88ba039d86cf309b84a1a3456654e25968 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:54:33 +0200 Subject: [PATCH 033/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Credt=20usage=20a?= =?UTF-8?q?t=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/HomeController.php | 2 +- app/Models/Product.php | 16 +++++++++++++++- app/Models/User.php | 8 +++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index d6b2fd18d..80c1ff5ef 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -76,7 +76,7 @@ public function index(Request $request) /** Build our Time-Left-Box */ if ($credits > 0.01 and $usage > 0) { - $daysLeft = number_format(($credits * 30) / $usage, 2, '.', ''); + $daysLeft = number_format($credits / ($usage / 30), 2, '.', ''); $hoursLeft = number_format($credits / ($usage / 30 / 24), 2, '.', ''); $bg = $this->getTimeLeftBoxBackground($daysLeft); diff --git a/app/Models/Product.php b/app/Models/Product.php index 52a37c28c..2c23e9c90 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -37,7 +37,21 @@ public static function boot() public function getHourlyPrice() { - return ($this->price / 30) / 24; + // calculate the hourly price with the billing period + switch($this->billing_period) { + case 'daily': + return $this->price / 24; + case 'weekly': + return $this->price / 24 / 7; + case 'monthly': + return $this->price / 24 / 30; + case 'half-annually': + return $this->price / 24 / 30 / 6; + case 'annually': + return $this->price / 24 / 365; + default: + return $this->price; + } } public function getDailyPrice() diff --git a/app/Models/User.php b/app/Models/User.php index 5f8af0246..e4e3fc665 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -233,17 +233,19 @@ public function getAvatar() * @return string */ public function creditUsage() - { + { $usage = 0; foreach ($this->getServersWithProduct() as $server) { - $usage += $server->product->price; + $usage += $server->product->getHourlyPrice() * 24 * 30; } return number_format($usage, 2, '.', ''); - } + } private function getServersWithProduct() { return $this->servers() + ->whereNull('suspended') + ->whereNull('cancelled') ->with('product') ->get(); } From 4b276651502e4ac22151a00ded759684824a0ce2 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 05:06:38 +0200 Subject: [PATCH 034/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Disable=20Cancel?= =?UTF-8?q?=20Button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 0fdac5a2f..862174f3d 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -199,7 +199,7 @@ class="btn btn-info text-center float-left ml-2" </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" - disabled="{{ $server->suspended }}" + {{ $server->suspended? "disabled" : "" }} data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-4"></i> </button> From 3e4a4f32fcdad7fd6f3a5404a76630cccb1bb022 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 05:12:06 +0200 Subject: [PATCH 035/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Disable=20cancel?= =?UTF-8?q?=20button=20when=20cancelled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 862174f3d..8ccffc874 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -199,7 +199,7 @@ class="btn btn-info text-center float-left ml-2" </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" - {{ $server->suspended? "disabled" : "" }} + {{ $server->suspended || $server->cancelled ? "disabled" : "" }} data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-4"></i> </button> From 479ccdb92c5cc4746e9aa1c6597dc4d51854a565 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 05:26:48 +0200 Subject: [PATCH 036/514] =?UTF-8?q?chore:=20=F0=9F=8C=90=20Added=20Bold=20?= =?UTF-8?q?text=20for=20No=20refund?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/lang/de.json | 2 +- resources/lang/en.json | 2 +- resources/views/servers/index.blade.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lang/de.json b/resources/lang/de.json index 184785c6b..1beefcc00 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -454,7 +454,7 @@ "Cancelled": "Gekündigt", "An exception has occurred while trying to cancel the server": "Ein Fehler ist aufgetreten beim Versuch, den Server zu kündigen", "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "Dies wird Ihren aktuellen Server zur nächsten Abrechnungsperiode kündigen. Er wird beim Ablauf der aktuellen Periode gesperrt.", - "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.": "Dies ist eine irreversibel Aktion, alle Dateien dieses Servers werden gelöscht. Keine Gelder werden zurückgezahlt. Wir empfehlen, den Server zu löschen, wenn er gesperrt ist.", + "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.": "Dies ist eine irreversiblen Aktion, alle Dateien dieses Servers werden gelöscht. <strong>Keine Rückerstattung!</strong>. Wir empfehlen, den Server zu löschen, wenn er gesperrt ist.", "Cancel Server?": "Server kündigen?", "Delete Server?": "Server löschen?", "Billing Period": "Abrechnungsperiode", diff --git a/resources/lang/en.json b/resources/lang/en.json index 430bb7d29..035f66ac8 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -458,7 +458,7 @@ "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.", "Cancel Server?": "Cancel Server?", "Delete Server?": "Delete Server?", - "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.": "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.", + "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.": "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.", "Billing Period": "Billing Period", "Next Billing Cycle": "Next Billing Cycle", "Manage Server": "Manage Server", diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 8ccffc874..53490a292 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -248,7 +248,7 @@ class="btn btn-danger text-center float-right mr-2" const handleServerDelete = (serverId) => { Swal.fire({ title: "{{ __('Delete Server?') }}", - text: "{{ __('This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.') }}", + html: "{{!! __('This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.') !!}}", icon: 'warning', confirmButtonColor: '#d9534f', showCancelButton: true, From 1fb855a6840ba96436be7b23805827076ff588ac Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 05:31:05 +0200 Subject: [PATCH 037/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Quartely=20?= =?UTF-8?q?billing=20period?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 3 +++ app/Http/Controllers/Admin/ProductController.php | 4 ++-- app/Models/Product.php | 2 ++ resources/lang/de.json | 1 + resources/lang/en.json | 1 + resources/views/admin/products/create.blade.php | 3 +++ resources/views/admin/products/edit.blade.php | 4 ++++ resources/views/servers/index.blade.php | 5 +++++ 8 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index dac01f0f8..72b5bab4d 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -67,6 +67,9 @@ public function handle() case 'half-annually': $newBillingDate = Carbon::parse($server->last_billed)->addMonths(6); break; + case 'quarterly': + $newBillingDate = Carbon::parse($server->last_billed)->addMonths(3); + break; case 'monthly': $newBillingDate = Carbon::parse($server->last_billed)->addMonth(); break; diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index b24c5cc6b..9be326cdd 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -73,7 +73,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly,half-annually,annually", + "billing_period" => "required|in:hourly,daily,weekly,monthly,quarterly,half-annually,annually", ]); $disabled = !is_null($request->input('disabled')); @@ -140,7 +140,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly,half-annually,annually", + "billing_period" => "required|in:hourly,daily,weekly,monthly,quarterly,half-annually,annually", ]); $disabled = !is_null($request->input('disabled')); diff --git a/app/Models/Product.php b/app/Models/Product.php index 2c23e9c90..af8fd27a3 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -45,6 +45,8 @@ public function getHourlyPrice() return $this->price / 24 / 7; case 'monthly': return $this->price / 24 / 30; + case 'quarterly': + return $this->price / 24 / 30 / 3; case 'half-annually': return $this->price / 24 / 30 / 6; case 'annually': diff --git a/resources/lang/de.json b/resources/lang/de.json index 1beefcc00..d0b5408ae 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -448,6 +448,7 @@ "yearly": "Jährlich", "daily": "Täglich", "weekly": "Wöchentlich", + "quarterly": "Vierteljährlich", "half-annually": "Halbjährlich", "annually": "Jährlich", "Suspended": "Gesperrt", diff --git a/resources/lang/en.json b/resources/lang/en.json index 035f66ac8..0157b0f9a 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -450,6 +450,7 @@ "yearly": "Yearly", "daily": "Daily", "weekly": "Weekly", + "quarterly": "Quarterly", "half-annually": "Half-annually", "annually": "Annually", "Suspended": "Suspended", diff --git a/resources/views/admin/products/create.blade.php b/resources/views/admin/products/create.blade.php index f44f64f41..81ccf32dd 100644 --- a/resources/views/admin/products/create.blade.php +++ b/resources/views/admin/products/create.blade.php @@ -183,6 +183,9 @@ class="fas fa-info-circle"></i></label> <option value="monthly"> {{__('Monthly')}} </option> + <option value="quarterly"> + {{__('Quarterly')}} + </option> <option value="half-annually"> {{__('Half Annually')}} </option> diff --git a/resources/views/admin/products/edit.blade.php b/resources/views/admin/products/edit.blade.php index 9d3aec529..851142af3 100644 --- a/resources/views/admin/products/edit.blade.php +++ b/resources/views/admin/products/edit.blade.php @@ -190,6 +190,10 @@ class="fas fa-info-circle"></i></label> @endif> {{__('Monthly')}} </option> + <option value="quarterly" @if ($product->billing_period == 'quarterly') selected + @endif> + {{__('Quarterly')}} + </option> <option value="half-annually" @if ($product->billing_period == 'half-annually') selected @endif> {{__('Half Annually')}} diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 53490a292..a9c57994e 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -145,6 +145,9 @@ class="fas fa-info-circle"></i> @case('hourly') {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} @break + @case('quarterly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(3)->toDayDateTimeString(); }} + @break @case('half-annually') {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} @break @@ -172,6 +175,8 @@ class="fas fa-info-circle"></i> {{ __('per Month') }} @elseif($server->product->billing_period == 'half-annually') {{ __('per 6 Months') }} + @elseif($server->product->billing_period == 'quarterly') + {{ __('per 3 Months') }} @elseif($server->product->billing_period == 'annually') {{ __('per Year') }} @elseif($server->product->billing_period == 'weekly') From de96d6d6d3ceaaf6bc17b51693d9d8beba2253cb Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Wed, 27 Jul 2022 23:26:03 +0200 Subject: [PATCH 038/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20Fixed=20?= =?UTF-8?q?credits=20check=20at=20server=20creation=20&=20formatted=20pric?= =?UTF-8?q?es=20nicely?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 2 +- app/Http/Controllers/ServerController.php | 4 +++- resources/views/servers/create.blade.php | 15 ++++++++++++--- resources/views/servers/index.blade.php | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 72b5bab4d..014af9636 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -91,7 +91,7 @@ public function handle() } // check if the server is canceled or if user has enough credits to charge the server or - if ( $server->cancelled || $user->credits < $product->price) { + if ( $server->cancelled || $user->credits <= $product->price) { try { // suspend server $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 167325887..a740fbdb3 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -105,11 +105,13 @@ private function validateConfigurationRules() // minimum credits if (FacadesRequest::has("product")) { $product = Product::findOrFail(FacadesRequest::input("product")); + if ( Auth::user()->credits < ($product->minimum_credits == -1 ? config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50) - : $product->minimum_credits) + : $product->minimum_credits) || + Auth::user()->credits <= $product->price ) { return redirect()->route('servers.index')->with('error', "You do not have the required amount of " . CREDITS_DISPLAY_NAME . " to use this product!"); } diff --git a/resources/views/servers/create.blade.php b/resources/views/servers/create.blade.php index 02ab8915a..d91499e65 100644 --- a/resources/views/servers/create.blade.php +++ b/resources/views/servers/create.blade.php @@ -223,10 +223,10 @@ class="custom-select"> </div> </div> <button type="submit" x-model="selectedProduct" name="product" - :disabled="product.minimum_credits > user.credits" - :class="product.minimum_credits > user.credits ? 'disabled' : ''" + :disabled="product.minimum_credits > user.credits || product.price > user.credits" + :class="product.minimum_credits > user.credits || product.price > user.credits ? 'disabled' : ''" class="btn btn-primary btn-block mt-2" @click="setProduct(product.id)" - x-text=" product.minimum_credits > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : '{{ __('Create server') }}'"> + x-text="product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : '{{ __('Create server') }}'"> </button> </div> </div> @@ -354,6 +354,7 @@ function serverApp() { .catch(console.error) this.fetchedProducts = true; + // TODO: Sortable by user chosen property (cpu, ram, disk...) this.products = response.data.sort((p1, p2) => p1.price > p2.price && 1 || -1) @@ -362,11 +363,19 @@ function serverApp() { product.cpu = product.cpu / 100; }) + //format price to have no decimals if it is a whole number + this.products.forEach(product => { + if (product.price % 1 === 0) { + product.price = Math.round(product.price); + } + }) + this.loading = false; this.updateSelectedObjects() }, + /** * @description map selected id's to selected objects * @note being used in the server info box diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index a9c57994e..0e5d69505 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -188,7 +188,7 @@ class="fas fa-info-circle"></i> @endif </div> <span> - {{ $server->product->price }} + {{ number_format($server->product->price) }} </span> </div> </div> From b80a8640deddeec7c6f2e5763d739e5cfcd957b5 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 28 Jul 2022 23:20:39 +0200 Subject: [PATCH 039/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Server=20creation?= =?UTF-8?q?=20fail=20when=20server.price=20=3D=20user.credits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index a740fbdb3..e443f64ee 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -111,7 +111,7 @@ private function validateConfigurationRules() ($product->minimum_credits == -1 ? config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50) : $product->minimum_credits) || - Auth::user()->credits <= $product->price + Auth::user()->credits < $product->price ) { return redirect()->route('servers.index')->with('error', "You do not have the required amount of " . CREDITS_DISPLAY_NAME . " to use this product!"); } From e3e7329d312a6d9d73a52db4f1976b6917736f36 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 28 Jul 2022 23:33:04 +0200 Subject: [PATCH 040/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20See=20last=20comm?= =?UTF-8?q?it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index e443f64ee..f51dfb5ba 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -106,11 +106,12 @@ private function validateConfigurationRules() if (FacadesRequest::has("product")) { $product = Product::findOrFail(FacadesRequest::input("product")); + error_log(Auth::user()->credits); + error_log($product->price); + + error_log(Auth::user()->credits < $product->price ? "true" : "false"); if ( - Auth::user()->credits < - ($product->minimum_credits == -1 - ? config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50) - : $product->minimum_credits) || + Auth::user()->credits < $product->minimum_credits || Auth::user()->credits < $product->price ) { return redirect()->route('servers.index')->with('error', "You do not have the required amount of " . CREDITS_DISPLAY_NAME . " to use this product!"); From fd216654930ddeafb402ad9765f51de807c29420 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Mon, 15 Aug 2022 14:52:49 +0200 Subject: [PATCH 041/514] =?UTF-8?q?fix:=20=F0=9F=92=84=20Styling=20of=20se?= =?UTF-8?q?rver=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 43bdc558a..223bbab2c 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -200,23 +200,25 @@ class="fas fa-info-circle"></i> target="__blank" class="btn btn-info text-center float-left ml-2" data-toggle="tooltip" data-placement="bottom" title="{{ __('Manage Server') }}"> - <i class="fas fa-tools mx-4"></i> + <i class="fas fa-tools mx-2"></i> </a> - <a href="{{ route('servers.show', ['server' => $server->id])}}" - class="btn btn-info mx-3 w-100 align-items-center justify-content-center d-flex" + @if(config("SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN")) + <a href="{{ route('servers.show', ['server' => $server->id])}}" + class="btn btn-info text-center mr-3" data-toggle="tooltip" data-placement="bottom" title="{{ __('Server Settings') }}"> - <i class="fas fa-cog mr-2"></i> + <i class="fas fa-cog mx-2"></i> </a> + @endif <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" {{ $server->suspended || $server->cancelled ? "disabled" : "" }} data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> - <i class="fas fa-ban mx-4"></i> + <i class="fas fa-ban mx-2"></i> </button> <button onclick="handleServerDelete('{{ $server->id }}');" target="__blank" class="btn btn-danger text-center float-right mr-2" data-toggle="tooltip" data-placement="bottom" title="{{ __('Delete Server') }}"> - <i class="fas fa-trash mx-4"></i> + <i class="fas fa-trash mx-2"></i> </button> </div> </div> From b580e47cdf1e8c3eb3fb80ac20915453e5b38a2f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Mon, 15 Aug 2022 14:56:34 +0200 Subject: [PATCH 042/514] chore: Docs --- Addon-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Addon-notes.md b/Addon-notes.md index baf40c6aa..1d3887428 100644 --- a/Addon-notes.md +++ b/Addon-notes.md @@ -1,3 +1,3 @@ Export diff files: -git diff -r --no-commit-id --name-only --diff-filter=ACMR <commit> | tar -czf file.tgz -T - +git diff -r --no-commit-id --name-only --diff-filter=ACMR <commit> | tar -czf ../controllpanelgg-monthly-addon/file.tgz -T - From bacab7bb18c47b7da39bc61355e5f20a9856a919 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Wed, 16 Nov 2022 18:10:00 +0100 Subject: [PATCH 043/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Fix=20cpgg=20bad?= =?UTF-8?q?=20code=20bug=20->=20doesNotFit=20can=20be=20undefined...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/create.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/servers/create.blade.php b/resources/views/servers/create.blade.php index cc0f95ddd..4b4c79593 100644 --- a/resources/views/servers/create.blade.php +++ b/resources/views/servers/create.blade.php @@ -228,8 +228,8 @@ class="custom-select"> </div> </div> <button type="submit" x-model="selectedProduct" name="product" - :disabled="product.minimum_credits > user.credits || product.price > user.credits || product.doesNotFit" - :class="product.minimum_credits > user.credits || product.price > user.credits ? 'disabled' : ''" + :disabled="product.doesNotFit || product.minimum_credits > user.credits || product.price > user.credits" + :class="product.doesNotFit || product.minimum_credits > user.credits || product.price > user.credits ? 'disabled' : ''" class="btn btn-primary btn-block mt-2" @click="setProduct(product.id)" x-text="product.doesNotFit ? '{{ __('Server cannot fit on this node') }}' : product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : '{{ __('Create server') }}'"> </button> From 0f5450e01e27ad53326765434bc731377808b94d Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Tue, 22 Nov 2022 12:23:33 +0100 Subject: [PATCH 044/514] fix: Number formatting on server overview (price) --- Addon-notes.md | 3 ++- resources/views/servers/index.blade.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Addon-notes.md b/Addon-notes.md index 1d3887428..bed6f5a2e 100644 --- a/Addon-notes.md +++ b/Addon-notes.md @@ -1,3 +1,4 @@ Export diff files: +Commit Hash of lates Main commit -git diff -r --no-commit-id --name-only --diff-filter=ACMR <commit> | tar -czf ../controllpanelgg-monthly-addon/file.tgz -T - +git diff -r --no-commit-id --name-only --diff-filter=ACMR \<commit> | tar -czf ../controllpanelgg-monthly-addon/file.tgz -T - diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 4b334f51a..4aca432ce 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -189,7 +189,7 @@ class="fas fa-info-circle"></i> @endif </div> <span> - {{ number_format($server->product->price) }} + {{ $server->product->price == round($server->product->price) ? round($server->product->price) : $server->product->price }} </span> </div> </div> From e5ae179b9d23e3d43e472ae1570c378c22923a15 Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Tue, 22 Nov 2022 13:42:46 +0100 Subject: [PATCH 045/514] Fixed Upgrade/Downgrade Credit withdrawal --- app/Http/Controllers/ServerController.php | 61 ++++++++++++++++------ resources/views/servers/settings.blade.php | 10 ++-- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 00e2aea01..91ef195bd 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -9,6 +9,7 @@ use App\Models\Node; use App\Models\Product; use App\Models\Server; +use App\Models\User; use App\Models\Settings; use App\Notifications\ServerCreationError; use Carbon\Carbon; @@ -219,8 +220,6 @@ private function noAllocationsError(Server $server) */ private function serverCreationFailed(Response $response, Server $server) { - $server->delete(); - return redirect()->route('servers.index')->with('error', json_encode($response->json())); } @@ -254,7 +253,7 @@ public function show(Server $server) { - if($server->user_id != Auth::user()->id){ return back()->with('error', __('´This is not your Server!'));} + if($server->user_id != Auth::user()->id){ return back()->with('error', __('This is not your Server!'));} $serverAttributes = Pterodactyl::getServerAttributes($server->pterodactyl_id); $serverRelationships = $serverAttributes['relationships']; $serverLocationAttributes = $serverRelationships['location']['attributes']; @@ -293,7 +292,7 @@ public function show(Server $server) public function upgrade(Server $server, Request $request) { - if($server->user_id != Auth::user()->id) return redirect()->route('servers.index'); + if($server->user_id != Auth::user()->id || $server->suspended) return redirect()->route('servers.index'); if(!isset($request->product_upgrade)) { return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('this product is the only one')); @@ -315,24 +314,54 @@ public function upgrade(Server $server, Request $request) $checkResponse = Pterodactyl::checkNodeResources($node, $requireMemory, $requiredisk); if ($checkResponse == False) return redirect()->route('servers.index')->with('error', __("The node '" . $nodeName . "' doesn't have the required memory or disk left to upgrade the server.")); - $priceupgrade = $newProduct->getHourlyPrice(); - - if ($priceupgrade < $oldProduct->getHourlyPrice()) { - $priceupgrade = 0; - } - if ($user->credits >= $priceupgrade && $user->credits >= $newProduct->minimum_credits) + // calculate the amount of credits that the user overpayed for the old product when canceling the server right now + // billing periods are hourly, daily, weekly, monthly, quarterly, half-annually, annually + $billingPeriod = $oldProduct->billing_period; + // seconds + $billingPeriods = [ + 'hourly' => 3600, + 'daily' => 86400, + 'weekly' => 604800, + 'monthly' => 2592000, + 'quarterly' => 7776000, + 'half-annually' => 15552000, + 'annually' => 31104000 + ]; + // Get the amount of hours the user has been using the server + $billingPeriodMultiplier = $billingPeriods[$billingPeriod]; + $timeDifference = now()->diffInSeconds($server->last_billed); + + error_log("Time DIFFERENCE!!!! ",$timeDifference); + // Calculate the price for the time the user has been using the server + $overpayedCredits = $oldProduct->price - $oldProduct->price * ($timeDifference / $billingPeriodMultiplier); + + + if ($user->credits >= $newProduct->price && $user->credits >= $newProduct->minimum_credits) { - - $server->product_id = $request->product_upgrade; - $server->update(); $server->allocation = $serverAttributes['allocation']; + // Update the server on the panel $response = Pterodactyl::updateServer($server, $newProduct); if ($response->failed()) return $this->serverCreationFailed($response, $server); - //update user balance - $user->decrement('credits', $priceupgrade); + + // Remove the allocation property from the server object as it is not a column in the database + unset($server->allocation); + // Update the server on controlpanel + $server->update([ + 'product_id' => $newProduct->id, + 'updated_at' => now(), + 'last_billed' => now(), + 'cancelled' => null, + ]); + + // Refund the user the overpayed credits + if ($overpayedCredits > 0) $user->increment('credits', $overpayedCredits); + + // Withdraw the credits for the new product + $user->decrement('credits', $newProduct->price); + //restart the server $response = Pterodactyl::powerAction($server, "restart"); - if ($response->failed()) return redirect()->route('servers.index')->with('error', $response->json()['errors'][0]['detail']); + if ($response->failed()) return redirect()->route('servers.index')->with('error', 'Server upgraded successfully! Could not restart the server: '.$response->json()['errors'][0]['detail']); return redirect()->route('servers.show', ['server' => $server->id])->with('success', __('Server Successfully Upgraded')); } else diff --git a/resources/views/servers/settings.blade.php b/resources/views/servers/settings.blade.php index 1a1b269b9..edad1f7fb 100644 --- a/resources/views/servers/settings.blade.php +++ b/resources/views/servers/settings.blade.php @@ -242,10 +242,7 @@ class="btn btn-info btn-md"> </button> </div> <div class="modal-body card-body"> - <strong>{{__("FOR DOWNGRADE PLEASE CHOOSE A PLAN BELOW YOUR PLAN")}}</strong> - <br> - <br> - <strong>{{__("YOUR PRODUCT")}} : </strong> {{ $server->product->name }} + <strong>{{__("Current Product")}}: </strong> {{ $server->product->name }} <br> <br> @@ -255,12 +252,13 @@ class="btn btn-info btn-md"> <option value="">{{__("Select the product")}}</option> @foreach($products as $product) @if(in_array($server->egg, $product->eggs) && $product->id != $server->product->id && $product->disabled == false) - <option value="{{ $product->id }}" @if($product->doesNotFit)disabled @endif>{{ $product->name }} [ {{ CREDITS_DISPLAY_NAME }} {{ $product->price }} @if($product->doesNotFit)] {{__('Server can´t fit on this node')}} @else @if($product->minimum_credits!=-1) / + <option value="{{ $product->id }}" @if($product->doesNotFit)disabled @endif>{{ $product->name }} [ {{ CREDITS_DISPLAY_NAME }} {{ $product->price }} @if($product->doesNotFit)] {{__('Server can\'t fit on this node')}} @else @if($product->minimum_credits!=-1) / {{__("Required")}}: {{$product->minimum_credits}} {{ CREDITS_DISPLAY_NAME }}@endif ] @endif</option> @endif @endforeach </select> - <br> {{__("Once the Upgrade button is pressed, we will automatically deduct the amount for the first hour according to the new product from your credits")}}. <br> + + <br> <strong>{{__("Caution") }}:</strong> {{__("Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed")}}. <br> <br> {{__("Server will be automatically restarted once upgraded")}} </div> <div class="modal-footer card-body"> From a7c47bff64cc862892b19e383bcb062b6a5776a1 Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Tue, 22 Nov 2022 13:43:06 +0100 Subject: [PATCH 046/514] =?UTF-8?q?chore:=20=F0=9F=8C=90=20localization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/lang/bg.json | 20 +++++++++++--------- resources/lang/bs.json | 18 ++++++++++-------- resources/lang/cs.json | 24 +++++++++++++----------- resources/lang/de.json | 18 ++++++++++-------- resources/lang/en.json | 26 ++++++++++++++------------ resources/lang/es.json | 22 ++++++++++++---------- resources/lang/fr.json | 18 ++++++++++-------- resources/lang/he.json | 42 ++++++++++++++++++++++-------------------- resources/lang/hi.json | 18 ++++++++++-------- resources/lang/hu.json | 18 ++++++++++-------- resources/lang/it.json | 18 ++++++++++-------- resources/lang/nl.json | 18 ++++++++++-------- resources/lang/pl.json | 18 ++++++++++-------- resources/lang/pt.json | 18 ++++++++++-------- resources/lang/ro.json | 18 ++++++++++-------- resources/lang/ru.json | 20 +++++++++++--------- resources/lang/sh.json | 16 +++++++++------- resources/lang/sk.json | 18 ++++++++++-------- resources/lang/sr.json | 18 ++++++++++-------- resources/lang/sv.json | 18 ++++++++++-------- resources/lang/tr.json | 18 ++++++++++-------- resources/lang/zh.json | 18 ++++++++++-------- 22 files changed, 242 insertions(+), 198 deletions(-) diff --git a/resources/lang/bg.json b/resources/lang/bg.json index b710be84c..4c3340d33 100644 --- a/resources/lang/bg.json +++ b/resources/lang/bg.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Грешка при създаване на сървър", "Your servers have been suspended!": "Сървърите ви са спрени!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "За да активирате автоматично вашия сървър\/и, трябва да закупите повече кредити.", + "To automatically re-enable your server/s, you need to purchase more credits.": "За да активирате автоматично вашия сървър/и, трябва да закупите повече кредити.", "Purchase credits": "Купете кредити", "If you have any questions please let us know.": "При допълнителни въпроси, моля свържете се с нас.", "Regards": "Поздрави", @@ -93,7 +93,7 @@ "Getting started!": "Приготвяме се да започнем!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Език по подразбиране", "The fallback Language, if something goes wrong": "Резервният език, ако нещо се обърка", "Datable language": "Език с данни", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Езиков код на таблиците с данни. <br><strong>Пример:<\/strong> en-gb, fr_fr, de_de<br>Повече информация: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Езиков код на таблиците с данни. <br><strong>Пример:</strong> en-gb, fr_fr, de_de<br>Повече информация: ", "Auto-translate": "Автоматичен превод", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Ако това е отметнато, таблото за управление ще се преведе на езика на клиентите, ако е наличен", "Client Language-Switch": "Превключване на клиентски език", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Таксува кредити за първия час при създаване на сървър.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Въведете URL адреса на вашата инсталация на PHPMyAdmin. <strong>Без крайна наклонена черта!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Въведете URL адреса на вашата инсталация на PHPMyAdmin. <strong>Без крайна наклонена черта!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Въведете URL адреса на вашата инсталация на Pterodactyl.<strong>Без крайна наклонена черта!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Въведете URL адреса на вашата инсталация на Pterodactyl.<strong>Без крайна наклонена черта!</strong>", "Pterodactyl API Key": "Pterodactyl API Kлюч", "Enter the API Key to your Pterodactyl installation.": "Въведете API ключа към вашата инсталация на Pterodactyl.", "Force Discord verification": "Принудително потвърждаване на Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Ваучер може да се използва само веднъж на потребител. Uses определя броя на различните потребители, които могат да използват този ваучер.", "Max": "Макс", "Expires at": "Изтича на", - "Used \/ Uses": "Използван \/ Използвания", + "Used / Uses": "Използван / Използвания", "Expires": "Изтича", "Sign in to start your session": "Влезте, за да започнете сесията си", "Password": "Парола", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Няма свързани Node-ове!", "No nests available!": "Няма налични Nest-ове!", "No eggs have been linked!": "Няма свързани Egg-ове!", - "Software \/ Games": "Софтуер \/ Игри", + "Software / Games": "Софтуер / Игри", "Please select software ...": "Моля, изберете софтуер...", "---": "---", "Specification ": "Спецификация ", @@ -460,5 +460,7 @@ "tr": "Турски", "ru": "Руски", "sv": "Swedish", - "sk": "Slovakish" -} \ No newline at end of file + "sk": "Slovakish", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Актуализирането / надграждането на сървъра ви ще нулира вашата билнгова цикъл до сега. Вашите превишени кредити ще бъдат възстановени. Цената за новия билнгов цикъл ще бъде извлечена", + "Caution": "Внимание" +} diff --git a/resources/lang/bs.json b/resources/lang/bs.json index 1a5e73c37..1ee54bee1 100644 --- a/resources/lang/bs.json +++ b/resources/lang/bs.json @@ -80,7 +80,7 @@ "User ID": "User ID", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -173,7 +173,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -214,9 +214,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -284,7 +284,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -354,7 +354,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -441,5 +441,7 @@ "pl": "Polish", "zh": "Chinese", "tr": "Turkish", - "ru": "Russian" -} \ No newline at end of file + "ru": "Russian", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution" +} diff --git a/resources/lang/cs.json b/resources/lang/cs.json index 6e4eb0064..04dc0090e 100644 --- a/resources/lang/cs.json +++ b/resources/lang/cs.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Někdo se zaregistroval pomocí vašeho kódu!", "Server Creation Error": "Chyba při vytváření serveru", "Your servers have been suspended!": "Vaše servery byly pozastaveny!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Pro opětovné spuštění vašich serverů dobijte prosím kredity.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Pro opětovné spuštění vašich serverů dobijte prosím kredity.", "Purchase credits": "Zakoupit kredity", "If you have any questions please let us know.": "Máte-li jakékoli dotazy, dejte nám vědět.", "Regards": "S pozdravem", @@ -93,7 +93,7 @@ "Getting started!": "Začínáme!", "Welcome to our dashboard": "Vítejte v našem ovládacím panelu", "Verification": "Ověření", - "You can verify your e-mail address and link\/verify your Discord account.": "Můžete ověřit svojí e-mail adresu a přiojit váš Discord účet.", + "You can verify your e-mail address and link/verify your Discord account.": "Můžete ověřit svojí e-mail adresu a přiojit váš Discord účet.", "Information": "Informace", "This dashboard can be used to create and delete servers": "Tento panel může použít pro vytvoření a mazání serverů", "These servers can be used and managed on our pterodactyl panel": "Tyto servery můžete používat a spravovat v našem pterodactyl panelu", @@ -114,7 +114,7 @@ "Token": "Token", "Last used": "Naposledy použito", "Are you sure you wish to delete?": "Opravdu si přejete odstranit?", - "Nests": "Software\/hra", + "Nests": "Software/hra", "Sync": "Synchronizovat", "Active": "Aktivní", "ID": "ID", @@ -187,7 +187,7 @@ "Default language": "Výchozí jazyk", "The fallback Language, if something goes wrong": "Záložní jazyk, kdyby se něco pokazilo", "Datable language": "Jazyk tabulek", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Kód jazyka datových tabulek. <br><strong>Příklad:<\/strong> en-gb, fr_fr, de_de<br>Více informací: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Kód jazyka datových tabulek. <br><strong>Příklad:</strong> en-gb, fr_fr, de_de<br>Více informací: ", "Auto-translate": "Automatický překlad", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Pokud je tohle zaškrtlé, Panel bude přeložen do Jazyka klienta (pokud to je možné)", "Client Language-Switch": "Povolit uživatelům změnu jazyka", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Po vytvoření služby náčtuje ihned první hodinu.", "Credits Display Name": "Název kreditů", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Vložte URL vaší instalace PHPmyAdmin. <strong>Bez koncového lomítka!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Vložte URL vaší instalace PHPmyAdmin. <strong>Bez koncového lomítka!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Vložte URL vaší instalace Pterodactyl. <strong>Bez koncového lomítka!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Vložte URL vaší instalace Pterodactyl. <strong>Bez koncového lomítka!</strong>", "Pterodactyl API Key": "Pterodactyl API klíč", "Enter the API Key to your Pterodactyl installation.": "Zadejte API klíč Vaší Pterodactyl instalace.", "Force Discord verification": "Vynutit ověření skrz Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Poukaz může být použit pouze jednou na uživatele. Počet použití upravuje počet různých uživatelů, kteří můžou poukaz použít.", "Max": "Maximum", "Expires at": "Vyprší", - "Used \/ Uses": "Použito", + "Used / Uses": "Použito", "Expires": "Vyprší", "Sign in to start your session": "Pro pokračování se prosím přihlašte", "Password": "Heslo", @@ -386,9 +386,9 @@ "Sync now": "Synchronizovat nyní", "No products available!": "Žádné dostupné balíčky!", "No nodes have been linked!": "Nebyly propojeny žádné uzly!", - "No nests available!": "Žádný dostupný software\/hry!", + "No nests available!": "Žádný dostupný software/hry!", "No eggs have been linked!": "Nebyly nastaveny žádné distribuce!", - "Software \/ Games": "Software\/hry", + "Software / Games": "Software/hry", "Please select software ...": "Prosím zvolte software ...", "---": "----", "Specification ": "Specifikace ", @@ -460,5 +460,7 @@ "tr": "Turečtina", "ru": "Ruština", "sv": "Švédština", - "sk": "Slovensky" -} \ No newline at end of file + "sk": "Slovensky", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizace/snížení vašeho serveru resetuje váš fakturační cyklus na aktuální. Vaše přeplacené kredity budou vráceny. Cena za nový fakturační cyklus bude odečtena", + "Caution": "Upozornění" +} diff --git a/resources/lang/de.json b/resources/lang/de.json index 97988290b..1e8a8402d 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Jemand hat sich mit deinem Code registriert!", "Server Creation Error": "Fehler beim erstellen des Servers", "Your servers have been suspended!": "Deine Server wurden pausiert", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Um deine Server zu reaktivieren, musst du mehr Credits kaufen!", + "To automatically re-enable your server/s, you need to purchase more credits.": "Um deine Server zu reaktivieren, musst du mehr Credits kaufen!", "Purchase credits": "Credits kaufen", "If you have any questions please let us know.": "Solltest du weiter fragen haben, melde dich gerne beim Support!", "Regards": "mit freundlichen Grüßen", @@ -93,7 +93,7 @@ "Getting started!": "Den Anfang machen!", "Welcome to our dashboard": "Willkommen in unserem Dashboard", "Verification": "Verifizierung", - "You can verify your e-mail address and link\/verify your Discord account.": "Du kannst deine Email-Adresse und deinen Discord account verifizieren.", + "You can verify your e-mail address and link/verify your Discord account.": "Du kannst deine Email-Adresse und deinen Discord account verifizieren.", "Information": "Hinweis", "This dashboard can be used to create and delete servers": "Dieses Dashboard kann benutzt werden um Server zu erstellen und zu löschen", "These servers can be used and managed on our pterodactyl panel": "Die Server werden von unserem Pterodactyl-Panel aus gemanaged", @@ -187,7 +187,7 @@ "Default language": "Standardsprache", "The fallback Language, if something goes wrong": "Die Rückfall-Sprache, falls etwas schief geht", "Datable language": "Tabellensprache", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Der Sprachcode der Tabellensprache. <br><strong>Beispiel:<\/strong> en-gb, fr_fr, de_de<br>Weitere Informationen: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Der Sprachcode der Tabellensprache. <br><strong>Beispiel:</strong> en-gb, fr_fr, de_de<br>Weitere Informationen: ", "Auto-translate": "Automatisches übersetzen", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Wenn dies aktiviert ist, übersetzt sich das Dashboard selbst in die Sprache des Clients, sofern diese verfügbar ist", "Client Language-Switch": "Nutzer Sprachumschaltung", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Rechne den ersten stündlichen Anteil direkt bei Erstellung des Servers ab.", "Credits Display Name": "Credits Anzeigename", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Geben Sie die URL zu Ihrer PHPMyAdmin-Installation ein. <strong>Ohne abschließendendes Slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Geben Sie die URL zu Ihrer PHPMyAdmin-Installation ein. <strong>Ohne abschließendendes Slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Geben Sie die URL zu Ihrer Pterodactyl-Installation ein. <strong>Ohne abschließendendes Slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Geben Sie die URL zu Ihrer Pterodactyl-Installation ein. <strong>Ohne abschließendendes Slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Schlüssel", "Enter the API Key to your Pterodactyl installation.": "Geben Sie den API-Schlüssel zu Ihrer Pterodactyl-Installation ein.", "Force Discord verification": "Discord Verifikation erzwingen", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Ein Gutschein kann von einem User nur einmal eingelöst werden. \"Benutzungen\" setzt die Anzahl an Usern die diesen Gutschein einlösen können.", "Max": "Max", "Expires at": "Läuft ab am", - "Used \/ Uses": "Benutzungen", + "Used / Uses": "Benutzungen", "Expires": "Ablauf", "Sign in to start your session": "Melde dich an um das Dashboard zu benutzen", "Password": "Passwort", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Es wurde keine Nodes verknüpft", "No nests available!": "Keine Nests verfügbar", "No eggs have been linked!": "Es wurde keine Eggs verknüpft", - "Software \/ Games": "Software \/ Spiele", + "Software / Games": "Software / Spiele", "Please select software ...": "Bitte Software auswählen", "---": "--", "Specification ": "Spezifikation", @@ -483,5 +483,7 @@ "Cancel Server": "Server kündigen", "Yes, cancel it!": "Ja, löschen!", "No, abort!": "Abbrechen", - "Billing period": "Abrechnungsperiode" + "Billing period": "Abrechnungsperiode", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Das Upgrade/Downgrade Ihres Servers wird Ihre Abrechnungsperiode auf \"jetzt\" zurücksetzen. Ihre überzahlten Credits werden erstattet. Der Preis für die neue Abrechnungsperiode wird abgebucht.", + "Caution": "Achtung" } diff --git a/resources/lang/en.json b/resources/lang/en.json index 0e557bb71..07d646083 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -100,7 +100,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -112,7 +112,7 @@ "Getting started!": "Getting started!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -212,7 +212,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -269,9 +269,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Pterodactyl Admin-Account API Key": "Pterodactyl Admin-Account API Key", @@ -352,7 +352,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -409,7 +409,7 @@ "Actions": "Actions", "Add To Blacklist": "Add To Blacklist", "please make the best of it": "please make the best of it", - "Please note, the blacklist will make the user unable to make a ticket\/reply again": "Please note, the blacklist will make the user unable to make a ticket\/reply again", + "Please note, the blacklist will make the user unable to make a ticket/reply again": "Please note, the blacklist will make the user unable to make a ticket/reply again", "Ticket": "Ticket", "Category": "Category", "Last Updated": "Last Updated", @@ -439,7 +439,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -472,9 +472,9 @@ "Hourly Price": "Hourly Price", "Monthly Price": "Monthly Price", "MySQL Database": "MySQL Database", - "To enable the upgrade\/downgrade system, please set your Ptero Admin-User API Key in the Settings!": "To enable the upgrade\/downgrade system, please set your Ptero Admin-User API Key in the Settings!", - "Upgrade \/ Downgrade": "Upgrade \/ Downgrade", - "Upgrade\/Downgrade Server": "Upgrade\/Downgrade Server", + "To enable the upgrade/downgrade system, please set your Ptero Admin-User API Key in the Settings!": "To enable the upgrade/downgrade system, please set your Ptero Admin-User API Key in the Settings!", + "Upgrade / Downgrade": "Upgrade / Downgrade", + "Upgrade/Downgrade Server": "Upgrade/Downgrade Server", "FOR DOWNGRADE PLEASE CHOOSE A PLAN BELOW YOUR PLAN": "FOR DOWNGRADE PLEASE CHOOSE A PLAN BELOW YOUR PLAN", "YOUR PRODUCT": "YOUR PRODUCT", "Select the product": "Select the product", @@ -551,5 +551,7 @@ "Cancel Server": "Cancel Server", "Yes, cancel it!": "Yes, cancel it!", "No, abort!": "No, abort!", - "Billing period": "Billing period" + "Billing period": "Billing period", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution" } diff --git a/resources/lang/es.json b/resources/lang/es.json index af47f9bc3..818d8e132 100644 --- a/resources/lang/es.json +++ b/resources/lang/es.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Error de creación del servidor", "Your servers have been suspended!": "¡Sus servidores han sido suspendidos!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Para volver a habilitar automáticamente sus servidores, debe comprar más créditos.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Para volver a habilitar automáticamente sus servidores, debe comprar más créditos.", "Purchase credits": "Comprar Créditos", "If you have any questions please let us know.": "Si tienes más preguntas, por favor háznoslas saber.", "Regards": "Atentamente", @@ -93,7 +93,7 @@ "Getting started!": "¡Empezando!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Idioma predeterminado", "The fallback Language, if something goes wrong": "El lenguaje alternativo, si algo sale mal", "Datable language": "Lenguaje de tabla de datos", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "El código de idioma de las tablas de datos. <br><strong>Ejemplo:<\/strong> en-gb, fr_fr, de_de<br>Más información: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "El código de idioma de las tablas de datos. <br><strong>Ejemplo:</strong> en-gb, fr_fr, de_de<br>Más información: ", "Auto-translate": "Traducir automáticamente", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Si está marcado, el Tablero se traducirá solo al idioma del Cliente, si está disponible", "Client Language-Switch": "Cambio de idioma del cliente", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Carga la primera hora de créditos al crear un servidor.", "Credits Display Name": "Nombre de los Créditos para mostrar", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Ingrese la URL de su instalación de PHPMyAdmin. <strong>¡Sin una barra diagonal final!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Ingrese la URL de su instalación de PHPMyAdmin. <strong>¡Sin una barra diagonal final!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Introduzca la URL de su instalación de Pterodactyl. <strong>¡Sin una barra diagonal final!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Introduzca la URL de su instalación de Pterodactyl. <strong>¡Sin una barra diagonal final!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Ingrese la API Key para su instalación de Pterodactyl.", "Force Discord verification": "Forzar verificación de Discord", @@ -263,7 +263,7 @@ "Select panel favicon": "Seleccionar favicon del panel", "Store": "Tienda", "Server Slots": "Server Slots", - "Currency code": "Código de divisa\/moneda", + "Currency code": "Código de divisa/moneda", "Checkout the paypal docs to select the appropriate code": "Consulte los documentos de PayPal para seleccionar el código apropiado", "Quantity": "Cantidad", "Amount given to the user after purchasing": "Importe dado al usuario después de la compra", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "El descuento solo se puede utilizar una vez por usuario. Los usos especifica el número de usuarios diferentes que pueden utilizar este cupón.", "Max": "Máx", "Expires at": "Expira el", - "Used \/ Uses": "Uso \/ Usos", + "Used / Uses": "Uso / Usos", "Expires": "Expira", "Sign in to start your session": "Iniciar sesión para comenzar", "Password": "Contraseña", @@ -388,7 +388,7 @@ "No nodes have been linked!": "¡No se han vinculado nodos!", "No nests available!": "¡No hay nidos disponibles!", "No eggs have been linked!": "¡No se han vinculado huevos!", - "Software \/ Games": "Software \/ Juegos", + "Software / Games": "Software / Juegos", "Please select software ...": "Seleccione el software...", "---": "---", "Specification ": "Especificación ", @@ -460,5 +460,7 @@ "tr": "Turco", "ru": "Ruso", "sv": "Sueco", - "sk": "Eslovaco" -} \ No newline at end of file + "sk": "Eslovaco", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Actualizar/Reducir el servidor restablecerá su ciclo de facturación a ahora. Se reembolsarán los créditos sobrepagados. El precio del nuevo ciclo de facturación se retirará", + "Caution": "Cuidado" +} diff --git a/resources/lang/fr.json b/resources/lang/fr.json index 7c21a7359..c59959a98 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Erreur lors de la création de votre serveur", "Your servers have been suspended!": "Votre serveur à été suspendu !", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Pour réactiver automatiquement votre ou vos serveurs, vous devez racheter des crédits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Pour réactiver automatiquement votre ou vos serveurs, vous devez racheter des crédits.", "Purchase credits": "Acheter des crédits", "If you have any questions please let us know.": "N'hésitez pas à nous contacter si vous avez des questions.", "Regards": "Cordialement", @@ -93,7 +93,7 @@ "Getting started!": "Commencer !", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Langue par défaut", "The fallback Language, if something goes wrong": "La langue de repli, si quelque chose ne va pas", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Le code ne peut être utilisé qu'une seule fois. L'utilisation spécifie le nombre d'utilisateurs différents qui peuvent utiliser ce code.", "Max": "Max", "Expires at": "Expire à", - "Used \/ Uses": "Utilisé \/ Utilisations", + "Used / Uses": "Utilisé / Utilisations", "Expires": "Expire", "Sign in to start your session": "Identifiez-vous pour commencer votre session", "Password": "Mot de passe", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Aucune node n'a été lié !", "No nests available!": "Aucun nests disponible !", "No eggs have been linked!": "Aucun eggs n'a été lié !", - "Software \/ Games": "Logiciels \/ Jeux", + "Software / Games": "Logiciels / Jeux", "Please select software ...": "Veuillez sélectionner...", "---": "---", "Specification ": "Spécification ", @@ -447,6 +447,8 @@ "Notes": "Notes", "Amount in words": "Montant en toutes lettres", "Please pay until": "Veuillez payer avant", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Mettre à niveau / Réduire votre serveur réinitialisera votre cycle de facturation à maintenant. Vos crédits surpayés seront remboursés. Le prix du nouveau cycle de facturation sera débité", + "Caution": "Attention", "cs": "Czech", "de": "German", "en": "English", @@ -461,4 +463,4 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/he.json b/resources/lang/he.json index da4d205bd..a64fde04a 100644 --- a/resources/lang/he.json +++ b/resources/lang/he.json @@ -60,8 +60,8 @@ "You ran out of Credits": "נגמר לך המטבעות", "Profile updated": "הפרופיל עודכן", "Server limit reached!": "הגעת להגבלת השרתים!", - "You are required to verify your email address before you can create a server.": "אתה מדרש לאמת את כתובת המייל שלך לפני שתוכל\/י ליצור שרת", - "You are required to link your discord account before you can create a server.": "אתה חייב לקשר את החשבון דיסקורד שלך לפני שתוכל\/י ליצור שרת", + "You are required to verify your email address before you can create a server.": "אתה מדרש לאמת את כתובת המייל שלך לפני שתוכל/י ליצור שרת", + "You are required to link your discord account before you can create a server.": "אתה חייב לקשר את החשבון דיסקורד שלך לפני שתוכל/י ליצור שרת", "Server created": "השרת נוצר", "No allocations satisfying the requirements for automatic deployment on this node were found.": "לא נמצאו הקצאות העומדות בדרישות לפריסה אוטומטית בשרת זה.", "You are required to verify your email address before you can purchase credits.": "אתה נדרש לאמת את כתובת המייל שלך לפני רכישת מטבעות", @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "שגיאה ביצירת שרת", "Your servers have been suspended!": "השרת שלך מושעה!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "כדי להפעיל מחדש את השרתים שלך באופן אוטומטי, עליך לרכוש מטבעות נוספות.", + "To automatically re-enable your server/s, you need to purchase more credits.": "כדי להפעיל מחדש את השרתים שלך באופן אוטומטי, עליך לרכוש מטבעות נוספות.", "Purchase credits": "לרכישת מטבעות", "If you have any questions please let us know.": "אם יש לכם כל שאלה תיידעו אותנו", "Regards": "בברכה", @@ -93,7 +93,7 @@ "Getting started!": "מתחילים!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -125,7 +125,7 @@ "Admin Overview": "סקירת מנהל", "Support server": "שרת תמיכה", "Documentation": "מדריך", - "Github": "Github\/גיטאהב", + "Github": "Github/גיטאהב", "Support ControlPanel": "תמיכת ControlPanel", "Servers": "שרתים", "Total": "בסך הכל", @@ -156,7 +156,7 @@ "Minimum": "מינימום", "Setting to -1 will use the value from configuration.": "הגדרות ל -1 ישתמש בערך מ configuration.", "IO": "IO", - "Databases": "Databases\/ממסד נתונים", + "Databases": "Databases/ממסד נתונים", "Backups": "גיבויים", "Allocations": "הקצאות", "Product Linking": "מוצרים מקושרים", @@ -187,7 +187,7 @@ "Default language": "שפת ברירת מחדל", "The fallback Language, if something goes wrong": "אם משהו משתבש", "Datable language": "שפה ניתנת לנתונים", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "טבלת הנתונים של השפות.<br><strong>לדוגמא:<\/strong> en-gb, fr_fr, de_de<br>למידע נוסף: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "טבלת הנתונים של השפות.<br><strong>לדוגמא:</strong> en-gb, fr_fr, de_de<br>למידע נוסף: ", "Auto-translate": "תרגום אוטומטי", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "אם זה מסומן, Dashboard יתרגם את עצמו לשפת הלקוח, אם זמין", "Client Language-Switch": "החלפת שפת לקוח", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Cגובה מטבעות בשווי השעה הראשונה בעת יצירת שרת.", "Credits Display Name": "שם המטבעות", "PHPMyAdmin URL": "קישור PHPMyAdmin", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "הכנס את הקישור to פיחפי. <strong>בלי צלייה נגררת!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "הכנס את הקישור to פיחפי. <strong>בלי צלייה נגררת!</strong>", "Pterodactyl URL": "קישור Pterodactyl", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API מפתח", "Enter the API Key to your Pterodactyl installation.": "הכנס את מפתח ה API Pterodactyl installation.", "Force Discord verification": "אימות דיסקורד חובה", @@ -300,12 +300,12 @@ "Notifications": "התראות", "All": "הכל", "Send via": "שליחה באמצאות", - "Database": "Database\/מאגר נתונים", + "Database": "Database/מאגר נתונים", "Content": "קשר", "Server limit": "הגבלת שרת", - "Discord": "Discord\/דיסקורד", + "Discord": "Discord/דיסקורד", "Usage": "נוהג", - "IP": "IP\/אייפי", + "IP": "IP/אייפי", "Referals": "Referals", "Vouchers": "קופונים", "Voucher details": "פרטי קופון", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "ניתן להשתמש בשובר פעם אחת בלבד לכל משתמש. שימושים מציינים את מספר המשתמשים השונים שיכולים להשתמש בשובר זה.", "Max": "מקסימום", "Expires at": "יפוג ב", - "Used \/ Uses": "משומש \/ שימושים", + "Used / Uses": "משומש / שימושים", "Expires": "פגי תוקף", "Sign in to start your session": "התחבר על מנת להתחיל", "Password": "סיסמה", @@ -339,7 +339,7 @@ "Before proceeding, please check your email for a verification link.": "לפני שתמשיך, אנא בדוק באימייל שלך קישור לאימות.", "If you did not receive the email": "אם לא קיבלת את המייל", "click here to request another": "לחץ כאן כדי לבקש אחר", - "per month": "\/חודש", + "per month": "/חודש", "Out of Credits in": "נגמרו המטבעות ב", "Home": "בית", "Language": "שפה", @@ -388,7 +388,7 @@ "No nodes have been linked!": "שרתים לא מקושרים!", "No nests available!": "No nests available!", "No eggs have been linked!": "אין eggs מקושרים", - "Software \/ Games": "תוכנה \/ משחקים", + "Software / Games": "תוכנה / משחקים", "Please select software ...": "בבקשה תחבר תוכנה ...", "---": "---", "Specification ": "ציין ", @@ -411,9 +411,9 @@ "Specification": "לציין", "Resource plan": "תוכנית משאבים", "RAM": "RAM", - "MySQL Databases": "בסיס הנתונים <bdi dir=\"ltr\">MySQL<\/bdi>", - "per Hour": "\/שעה", - "per Month": "\/חודש", + "MySQL Databases": "בסיס הנתונים <bdi dir=\"ltr\">MySQL</bdi>", + "per Hour": "/שעה", + "per Month": "/חודש", "Manage": "לנהל", "Are you sure?": "האם אתה בטוח?", "This is an irreversible action, all files of this server will be removed.": "זוהי פעולה בלתי הפיכה, כל הקבצים של שרת זה יוסרו.", @@ -460,5 +460,7 @@ "tr": "טורקית", "ru": "רוסית", "sv": "שוודית", - "sk": "סלובקית" -} \ No newline at end of file + "sk": "סלובקית", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "שדרוג / הורדת שרת יאפס את מחזור החיוב שלך לעכשיו. הקרדיטים ששילמת יוחזרו. המחיר למחזור החיוב החדש יוחסם", + "Caution": "אזהרה" +} diff --git a/resources/lang/hi.json b/resources/lang/hi.json index 68eba9f93..bdf5188a2 100644 --- a/resources/lang/hi.json +++ b/resources/lang/hi.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "सर्वर निर्माण त्रुटि", "Your servers have been suspended!": "आपके सर्वर निलंबित कर दिए गए हैं!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "अपने सर्वर\/सर्वर को स्वचालित रूप से पुन: सक्षम करने के लिए, आपको अधिक क्रेडिट खरीदने की आवश्यकता है।", + "To automatically re-enable your server/s, you need to purchase more credits.": "अपने सर्वर/सर्वर को स्वचालित रूप से पुन: सक्षम करने के लिए, आपको अधिक क्रेडिट खरीदने की आवश्यकता है।", "Purchase credits": "क्रेडिट खरीदें", "If you have any questions please let us know.": "यदि आपके पास कोई प्रश्न है, तो हमें बताएं।", "Regards": "सादर", @@ -93,7 +93,7 @@ "Getting started!": "शुरू करना!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "डिफ़ॉल्ट भाषा", "The fallback Language, if something goes wrong": "फ़ॉलबैक भाषा, अगर कुछ गलत हो जाता है", "Datable language": "डेटा योग्य भाषा", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "डेटाटेबल्स लैंग-कोड। <br><strong>उदाहरण:<\/strong> en-gb, fr_fr, de_de<br>अधिक जानकारी: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "डेटाटेबल्स लैंग-कोड। <br><strong>उदाहरण:</strong> en-gb, fr_fr, de_de<br>अधिक जानकारी: ", "Auto-translate": "ऑटो का अनुवाद", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "यदि यह चेक किया जाता है, तो डैशबोर्ड स्वयं को क्लाइंट भाषा में अनुवाद करेगा, यदि उपलब्ध हो", "Client Language-Switch": "क्लाइंट भाषा-स्विच", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "सर्वर बनाने पर पहले घंटे के क्रेडिट का शुल्क लेता है।", "Credits Display Name": "क्रेडिट प्रदर्शन नाम", "PHPMyAdmin URL": "PhpMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "अपने PHPMyAdmin इंस्टॉलेशन का URL दर्ज करें। <strong>पिछली स्लैश के बिना!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "अपने PHPMyAdmin इंस्टॉलेशन का URL दर्ज करें। <strong>पिछली स्लैश के बिना!</strong>", "Pterodactyl URL": "पटरोडैक्टाइल यूआरएल", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "अपने Pterodactyl संस्थापन का URL दर्ज करें। <strong>पिछली स्लैश के बिना!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "अपने Pterodactyl संस्थापन का URL दर्ज करें। <strong>पिछली स्लैश के बिना!</strong>", "Pterodactyl API Key": "पटरोडैक्टाइल एपीआई कुंजी", "Enter the API Key to your Pterodactyl installation.": "अपने Pterodactyl स्थापना के लिए API कुंजी दर्ज करें।", "Force Discord verification": "बल विवाद सत्यापन", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "वाउचर प्रति उपयोगकर्ता केवल एक बार उपयोग किया जा सकता है। उपयोग इस वाउचर का उपयोग करने वाले विभिन्न उपयोगकर्ताओं की संख्या को निर्दिष्ट करता है।", "Max": "मैक्स", "Expires at": "पर समाप्त हो रहा है", - "Used \/ Uses": "प्रयुक्त \/ उपयोग", + "Used / Uses": "प्रयुक्त / उपयोग", "Expires": "समय-सीमा समाप्त", "Sign in to start your session": "अपना सत्र शुरू करने के लिए साइन इन करें", "Password": "पासवर्ड", @@ -388,7 +388,7 @@ "No nodes have been linked!": "कोई नोड लिंक नहीं किया गया है!", "No nests available!": "कोई घोंसला उपलब्ध नहीं है!", "No eggs have been linked!": "कोई अंडे नहीं जोड़े गए हैं!", - "Software \/ Games": "सॉफ्टवेयर \/ खेल", + "Software / Games": "सॉफ्टवेयर / खेल", "Please select software ...": "कृपया सॉफ्टवेयर चुनें...", "---": "---", "Specification ": "विनिर्देश", @@ -447,6 +447,8 @@ "Notes": "टिप्पणियाँ", "Amount in words": "राशि शब्दों में", "Please pay until": "कृपया भुगतान करें", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "अपने सर्वर को अपग्रेड / डाउनग्रेड करने से आपका बिलिंग साइकिल अब तक रीसेट हो जाएगा। आपके ओवरपेड क्रेडिट वापस किया जाएगा। नए बिलिंग साइकिल के लिए की गई मूल्य निकाला जाएगा", + "Caution": "सावधान", "cs": "चेक", "de": "जर्मन", "en": "अंग्रेज़ी", @@ -461,4 +463,4 @@ "ru": "रूसी", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/hu.json b/resources/lang/hu.json index d6fbef0c3..87c6c7fca 100644 --- a/resources/lang/hu.json +++ b/resources/lang/hu.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Valaki regisztrált a te Kódoddal!", "Server Creation Error": "Hiba a szerver készítése közben", "Your servers have been suspended!": "A szervered fel lett függesztve!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "A szervered\/szervereid autómatikus újraengedélyezéséhez Kreditet kell vásárolnod.", + "To automatically re-enable your server/s, you need to purchase more credits.": "A szervered/szervereid autómatikus újraengedélyezéséhez Kreditet kell vásárolnod.", "Purchase credits": "Kreditek vásárlása", "If you have any questions please let us know.": "Ha kérdésed van, kérjük fordulj hozzánk.", "Regards": "Üdvözlettel", @@ -93,7 +93,7 @@ "Getting started!": "Kezdhetjük!", "Welcome to our dashboard": "Üdvözlünk az Irányítópultban", "Verification": "Hitelesítés", - "You can verify your e-mail address and link\/verify your Discord account.": "Hitelesíteni tudod az email címedet és a Discord fiókodat.", + "You can verify your e-mail address and link/verify your Discord account.": "Hitelesíteni tudod az email címedet és a Discord fiókodat.", "Information": "Információk", "This dashboard can be used to create and delete servers": "Ebben az Irányítópultban szervereket tudsz létrehozni és törölni", "These servers can be used and managed on our pterodactyl panel": "Ezeket a szervereket a Pterodactyl panelben tudod kezelni", @@ -187,7 +187,7 @@ "Default language": "Alapértelmezett nyelv", "The fallback Language, if something goes wrong": "A tartalék nyelv, ha bármi rosszul működne", "Datable language": "Keltezhető nyelv", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Az adattáblák kódnyelve. <br><strong>Például:<\/strong> en-gb, fr_fr, de_de<br>Több információ: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Az adattáblák kódnyelve. <br><strong>Például:</strong> en-gb, fr_fr, de_de<br>Több információ: ", "Auto-translate": "Autómatikus fordítás", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Ha be van kapcsolva, akkor az Irányítópult autómatikusan le lesz fordítva a Kliens által használt nyelvre, ha az elérhető", "Client Language-Switch": "Kliens nyelv váltás", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Első óra kifizetése szerver létrehozásnál.", "Credits Display Name": "Kredit megnevezése", "PHPMyAdmin URL": "PHPMyAdmin Hivatkozás", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Írd be a hivatkozást a PHPMyAdmin telepítéséhez. <strong>A zárjó perjel nélkül!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Írd be a hivatkozást a PHPMyAdmin telepítéséhez. <strong>A zárjó perjel nélkül!</strong>", "Pterodactyl URL": "Pterodactyl Hivatkozás", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Írd be a hivatkozást a Pterodactyl telepítéséhez. <strong>A zárjó perjel nélkül!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Írd be a hivatkozást a Pterodactyl telepítéséhez. <strong>A zárjó perjel nélkül!</strong>", "Pterodactyl API Key": "Pterodactyl API Kulcs", "Enter the API Key to your Pterodactyl installation.": "Írd be az API Kulcsot a Pterodactyl telepítéséhez.", "Force Discord verification": "Discord hitelesítés kötelezése", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Az utalványt egy felhasználó csak egyszer használhatja fel. Ezzel meg tudod adni, hogy hány felhasználó tudja felhasználni az utalványt.", "Max": "Max", "Expires at": "Lejárás ideje", - "Used \/ Uses": "Felhasználva \/ Felhasználások száma", + "Used / Uses": "Felhasználva / Felhasználások száma", "Expires": "Lejár", "Sign in to start your session": "Jelentkezz be a munkamenet elindításához", "Password": "Jelszó", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Nem lett csomópont hozzácsatolva!", "No nests available!": "Nincs elérhető fészek!", "No eggs have been linked!": "Nem lett tojás hozzácsatolva!", - "Software \/ Games": "Szoftver \/ Játékok", + "Software / Games": "Szoftver / Játékok", "Please select software ...": "Szoftver kiválasztása ...", "---": "---", "Specification ": "Specifikációk ", @@ -447,6 +447,8 @@ "Notes": "Jegyzetek", "Amount in words": "Mennyiség szavakban", "Please pay until": "Fizetési határidő", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "A szerver frissítése / lefrissítése visszaállítja a számlázási ciklust az aktuálisra. A túlfizetett Kreditet visszatérítjük. A számlázási ciklus új ára lesz kivonva", + "Caution": "Figyelem", "cs": "Cseh", "de": "Német", "en": "Angol", @@ -461,4 +463,4 @@ "ru": "Orosz", "sv": "Svéd", "sk": "Szlovák" -} \ No newline at end of file +} diff --git a/resources/lang/it.json b/resources/lang/it.json index 379bad4f3..21fabe898 100644 --- a/resources/lang/it.json +++ b/resources/lang/it.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Errore di creazione del server", "Your servers have been suspended!": "I tuoi server sono stati sospesi!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Per ri-abilitare i tuoi server automaticamente, devi acquistare più crediti.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Per ri-abilitare i tuoi server automaticamente, devi acquistare più crediti.", "Purchase credits": "Acquista crediti", "If you have any questions please let us know.": "Se hai una domanda faccelo sapere.", "Regards": "Cordialmente", @@ -93,7 +93,7 @@ "Getting started!": "Come iniziare!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Lingua predefinita", "The fallback Language, if something goes wrong": "La lingua secondaria, se qualcosa va storto", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Il lang-code dei datatables. <br><strong>Esempio:<\/strong> en-gb, fr_fr, de_de<br>Piu informazioni: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Il lang-code dei datatables. <br><strong>Esempio:</strong> en-gb, fr_fr, de_de<br>Piu informazioni: ", "Auto-translate": "Traduzione-automatica", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Se questo è abilitato la dashboard si traducerà automaticamente alla lingua dei clienti, se disponibile", "Client Language-Switch": "Switch delle lingue dei clienti", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Addebita la prima ora in crediti alla creazione di un server.", "Credits Display Name": "Nome di Visualizzazione dei Crediti", "PHPMyAdmin URL": "URL PHPMyAdmin", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Inserisci l'URL alla tua installazione di PHPMyAdmin. <strong>Senza lo slash finale!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Inserisci l'URL alla tua installazione di PHPMyAdmin. <strong>Senza lo slash finale!</strong>", "Pterodactyl URL": "URL di Pterodactyl", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Inserisci l'URL alla tua installazione di Pterodactyl. <strong>Senza un trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Inserisci l'URL alla tua installazione di Pterodactyl. <strong>Senza un trailing slash!</strong>", "Pterodactyl API Key": "Chiave API di Pterodactyl", "Enter the API Key to your Pterodactyl installation.": "Inserisci la Chiave API alla tua installazione di Pterodactyl.", "Force Discord verification": "Forza la verifica di Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Un voucher può essere utilizzato solo una volta per utente. Il numero di usi specifica il numero di utenti diversi che possono utilizzare questo voucher.", "Max": "Massimo", "Expires at": "Scade il", - "Used \/ Uses": "Usato \/ Utilizzi", + "Used / Uses": "Usato / Utilizzi", "Expires": "Scade", "Sign in to start your session": "Accedi per iniziare la sessione", "Password": "Password", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Nessun nodo è stato connesso!", "No nests available!": "Nessun nido (nest) disponibile!", "No eggs have been linked!": "Nessun uovo (egg) è stato connesso!", - "Software \/ Games": "Software \/ Giochi", + "Software / Games": "Software / Giochi", "Please select software ...": "Per favore selezione il software...", "---": "---", "Specification ": "Specifiche ", @@ -447,6 +447,8 @@ "Notes": "Note", "Amount in words": "Numero in parole", "Please pay until": "Per favore paga fino", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "L’aggiornamento / riduzione del tuo server reimposterà il tuo ciclo di fatturazione a ora. I tuoi crediti in eccesso saranno rimborsati. Il prezzo per il nuovo ciclo di fatturazione sarà prelevato", + "Caution": "Attenzione", "cs": "Ceco", "de": "Tedesco", "en": "Inglese", @@ -461,4 +463,4 @@ "ru": "Russo", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/nl.json b/resources/lang/nl.json index fa8eeed64..160bb0435 100644 --- a/resources/lang/nl.json +++ b/resources/lang/nl.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Fout bij het maken van de server", "Your servers have been suspended!": "Uw servers zijn opgeschort!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Om uw server(s) automatisch opnieuw in te schakelen, moet u meer credits kopen.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Om uw server(s) automatisch opnieuw in te schakelen, moet u meer credits kopen.", "Purchase credits": "Credits kopen", "If you have any questions please let us know.": "Als u vragen heeft, laat het ons dan weten.", "Regards": "Met vriendelijke groet", @@ -93,7 +93,7 @@ "Getting started!": "Aan de slag!", "Welcome to our dashboard": "Welkom bij ons dashboard", "Verification": "Verificatie", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Informatie", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Standaard taal", "The fallback Language, if something goes wrong": "De terugval-taal, als er iets misgaat", "Datable language": "Dateerbare taal", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "De datatabellen lang-code. <br><strong>Voorbeeld:<\/strong> nl-gb, fr_fr, de_de<br>Meer informatie: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "De datatabellen lang-code. <br><strong>Voorbeeld:</strong> nl-gb, fr_fr, de_de<br>Meer informatie: ", "Auto-translate": "Automatisch vertalen", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Als dit is aangevinkt, zal het dashboard zichzelf vertalen naar de taal van de klant, indien beschikbaar", "Client Language-Switch": "Klant Taal-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Brengt het eerste uur aan credits in rekening bij het maken van een server.", "Credits Display Name": "Weergavenaam tegoed", "PHPMyAdmin URL": "PHPMyAdmin-URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Voer de URL naar uw PHPMyAdmin-installatie in. <strong>Zonder een slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Voer de URL naar uw PHPMyAdmin-installatie in. <strong>Zonder een slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Voer de URL naar uw Pterodactyl-installatie in. <strong>Zonder een slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Voer de URL naar uw Pterodactyl-installatie in. <strong>Zonder een slash!</strong>", "Pterodactyl API Key": "Pterodactyl API sleutel", "Enter the API Key to your Pterodactyl installation.": "Voer de API-sleutel in voor uw Pterodactyl-installatie.", "Force Discord verification": "Forceer Discord Verificatie", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Een voucher kan slechts één keer per gebruiker worden gebruikt. Gebruik geeft het aantal verschillende gebruikers aan dat deze voucher kan gebruiken.", "Max": "Maximum", "Expires at": "Verloopt om", - "Used \/ Uses": "Gebruikt \/ Toepassingen", + "Used / Uses": "Gebruikt / Toepassingen", "Expires": "Verloopt", "Sign in to start your session": "Login om te beginnen", "Password": "Wachtwoord", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Er zijn geen nodes gekoppeld!", "No nests available!": "Er zijn geen nesten beschikbaar!", "No eggs have been linked!": "Geen eieren gekoppeld!", - "Software \/ Games": "Software \/ Spellen", + "Software / Games": "Software / Spellen", "Please select software ...": "Selecteer software...", "---": "---", "Specification ": "Specificatie ", @@ -447,6 +447,8 @@ "Notes": "Notities", "Amount in words": "Hoeveelheid in eenheden", "Please pay until": "Gelieve te betalen tot", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgraden/downgraden van uw server zal uw facturatiecyclus resetten naar nu. Uw overbetalen krediet zal worden terugbetaald. De prijs voor de nieuwe facturatiecyclus zal worden afgeschreven", + "Caution": "Let op", "cs": "Tsjechisch", "de": "Duits", "en": "Engels", @@ -461,4 +463,4 @@ "ru": "Russisch", "sv": "Zweeds", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/pl.json b/resources/lang/pl.json index df5538e94..ee3cf4995 100644 --- a/resources/lang/pl.json +++ b/resources/lang/pl.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Błąd podczas tworzenia serwera", "Your servers have been suspended!": "Serwery zostały zawieszone!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Aby reaktywować swoje serwery, musisz kupić więcej kredytów.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Aby reaktywować swoje serwery, musisz kupić więcej kredytów.", "Purchase credits": "Zakup kredyty", "If you have any questions please let us know.": "W razie jakichkolwiek pytań prosimy o kontakt.", "Regards": "Z poważaniem", @@ -93,7 +93,7 @@ "Getting started!": "Zaczynajmy!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Domyślny język", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Domyślny język", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Automatyczne tłumaczenie", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Nazwa Waluty", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "URL Pterodactyl Panelu", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Klucz API Pterodactyl panelu", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Kupon może zostać zrealizowany tylko raz przez użytkownika. „Użycie” określa liczbę użytkowników, którzy mogą zrealizować ten kupon.", "Max": "Max", "Expires at": "Wygasa", - "Used \/ Uses": "Użyto \/ Użyć", + "Used / Uses": "Użyto / Użyć", "Expires": "Wygasa", "Sign in to start your session": "Zaloguj się, aby rozpocząć sesję", "Password": "Hasło", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Żaden węzeł nie został połączony!", "No nests available!": "Brak dostępnych gniazd!", "No eggs have been linked!": "Jajka nie zostały połaczone!", - "Software \/ Games": "Oprogramowanie \/ gry", + "Software / Games": "Oprogramowanie / gry", "Please select software ...": "Proszę wybrać oprogramowanie...", "---": "---", "Specification ": "Specyfikacja ", @@ -447,6 +447,8 @@ "Notes": "Uwagi", "Amount in words": "Wszystkie słowa", "Please pay until": "Zapłać w ciągu:", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizacja / degradacja twojego serwera spowoduje zresetowanie cyklu rozliczeniowego do teraz. Twoje nadpłacone kredyty zostaną zwrócone. Cena za nowy cykl rozliczeniowy zostanie pobrana", + "Caution": "Uwaga", "cs": "Czeski", "de": "Niemiecki", "en": "Angielski", @@ -461,4 +463,4 @@ "ru": "Rosyjski", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/pt.json b/resources/lang/pt.json index 2313dc326..c301bcd8a 100644 --- a/resources/lang/pt.json +++ b/resources/lang/pt.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Alguém se registrou usando seu código!", "Server Creation Error": "Erro de criação do servidor", "Your servers have been suspended!": "Os seus servidores foram suspensos!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Para reativar automaticamente o seu(s) servidor(es), é preciso comprar mais créditos.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Para reativar automaticamente o seu(s) servidor(es), é preciso comprar mais créditos.", "Purchase credits": "Compra de créditos", "If you have any questions please let us know.": "Se tiver alguma dúvida, por favor, nos avise.", "Regards": "Cumprimentos,", @@ -93,7 +93,7 @@ "Getting started!": "Começar", "Welcome to our dashboard": "Bem-vindo ao nosso painel", "Verification": "Verificação", - "You can verify your e-mail address and link\/verify your Discord account.": "Você pode verificar o seu endereço de e-mail e link", + "You can verify your e-mail address and link/verify your Discord account.": "Você pode verificar o seu endereço de e-mail e link", "Information": "Informações", "This dashboard can be used to create and delete servers": "Este painel pode ser usado para criar e excluir servidores", "These servers can be used and managed on our pterodactyl panel": "Esses servidores podem ser usados e gerenciados no nosso painel pterodactyl ", @@ -187,7 +187,7 @@ "Default language": "Idioma padrão", "The fallback Language, if something goes wrong": "Um idioma padrão, se algo der errado", "Datable language": "Idioma de dados", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Os lang-codes disponíveis. <br><strong>Exemplo:<\/strong> en-gb, fr_fr, de_de<br>Mais informações:", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Os lang-codes disponíveis. <br><strong>Exemplo:</strong> en-gb, fr_fr, de_de<br>Mais informações:", "Auto-translate": "Traduzir Automaticamente", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Se isto for ativado, o Painel se traduzirá para o idioma do cliente, se disponível.", "Client Language-Switch": "Trocar Idioma do cliente", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Cobra a primeira hora de créditos ao criar um servidor.", "Credits Display Name": "Nome de exibição dos créditos", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Insira o URL do seu PHPMyAdmin. <strong>Sem barra de arrasto!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Insira o URL do seu PHPMyAdmin. <strong>Sem barra de arrasto!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Insira o URL do seu pterodactyl. <strong>Sem barra de arrasto!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Insira o URL do seu pterodactyl. <strong>Sem barra de arrasto!</strong>", "Pterodactyl API Key": "Chave API pterodactyl", "Enter the API Key to your Pterodactyl installation.": "Insira a chave API do seu painel pterodactyl.", "Force Discord verification": "Forçar verificação do Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Um vale só pode ser usado uma vez por utilizador. Os usos especificam o número de diferentes utilizadores que podem usar este comprovante.", "Max": "Máximo", "Expires at": "Expira em", - "Used \/ Uses": "Usados \/ Usos", + "Used / Uses": "Usados / Usos", "Expires": "Expira", "Sign in to start your session": "Entre para iniciar a sua sessão", "Password": "Senha", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Nenhum nó foi ligado!", "No nests available!": "Não há ninhos disponíveis!", "No eggs have been linked!": "Nenhum ovo foi ligado!", - "Software \/ Games": "“Software” \/ Jogos", + "Software / Games": "“Software” / Jogos", "Please select software ...": "Por favor, selecione o “software”...", "---": "—", "Specification ": "Especificação", @@ -447,6 +447,8 @@ "Notes": "Notas", "Amount in words": "Quantia em palavras", "Please pay until": "Favor pagar até", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Atualizar / Reduzir o seu servidor irá redefinir o seu ciclo de faturação para agora. Os seus créditos pagos a mais serão reembolsados. O preço para o novo ciclo de faturação será debitado", + "Caution": "Cuidado", "cs": "Tcheco", "de": "Alemão", "en": "Inglês", @@ -461,4 +463,4 @@ "ru": "Russo", "sv": "Sueco", "sk": "Eslovaco" -} \ No newline at end of file +} diff --git a/resources/lang/ro.json b/resources/lang/ro.json index b897bbd07..804eeb680 100644 --- a/resources/lang/ro.json +++ b/resources/lang/ro.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -93,7 +93,7 @@ "Getting started!": "Getting started!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -388,7 +388,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -447,6 +447,8 @@ "Notes": "Notes", "Amount in words": "Amount in words", "Please pay until": "Please pay until", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", "cs": "Czech", "de": "German", "en": "English", @@ -461,4 +463,4 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/ru.json b/resources/lang/ru.json index 90daf537e..f99779a3e 100644 --- a/resources/lang/ru.json +++ b/resources/lang/ru.json @@ -39,7 +39,7 @@ "Store item has been removed!": "Товар в магазине был удален!", "link has been created!": "Ссылка была создана!", "link has been updated!": "Ссылка была обновлена!", - "product has been removed!": "Продукт\/Товар был удалён!", + "product has been removed!": "Продукт/Товар был удалён!", "User does not exists on pterodactyl's panel": "Пользователь не был найден в панеле птеродактиль", "user has been removed!": "Пользователь был удален!", "Notification sent!": "Оповещение отправлено!", @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Ошибка создание сервера", "Your servers have been suspended!": "Ваши сервера были заблокированы!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Чтобы автоматически повторно включить ваш сервер \/ серверы, вам необходимо приобрести больше кредитов.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Чтобы автоматически повторно включить ваш сервер / серверы, вам необходимо приобрести больше кредитов.", "Purchase credits": "Приобрести кредиты", "If you have any questions please let us know.": "Пожалуйста, сообщите нам, если у Вас есть какие-либо вопросы.", "Regards": "С уважением,", @@ -93,7 +93,7 @@ "Getting started!": "Начало работы!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Язык по умолчанию", "The fallback Language, if something goes wrong": "Резервный язык, если что-то пойдет не так", "Datable language": "Датадатируемый язык", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Языковой код таблицы данных. <br><strong>Пример:<\/strong> en-gb, fr_fr, de_de<br>Дополнительная информация:", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Языковой код таблицы данных. <br><strong>Пример:</strong> en-gb, fr_fr, de_de<br>Дополнительная информация:", "Auto-translate": "Автоперевод", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Если этот флажок установлен, информационная панель будет переводиться на язык клиентов, если он доступен", "Client Language-Switch": "Переключение языка клиента", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Взимает кредиты за первый час при создании сервера.", "Credits Display Name": "Отображаемое имя кредитов", "PHPMyAdmin URL": "URL-адрес PHPMyAdmin", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Введите URL-адрес вашей установки PHPMyAdmin. <strong>Без косой черты в конце!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Введите URL-адрес вашей установки PHPMyAdmin. <strong>Без косой черты в конце!</strong>", "Pterodactyl URL": "URL-адрес птеродактиля", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Введите URL-адрес вашей установки Pterodactyl. <strong>Без косой черты в конце!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Введите URL-адрес вашей установки Pterodactyl. <strong>Без косой черты в конце!</strong>", "Pterodactyl API Key": "API-ключ птеродактиля", "Enter the API Key to your Pterodactyl installation.": "Введите ключ API для установки Pterodactyl.", "Force Discord verification": "Требуется верификация в Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Промокод можно использовать только один раз для каждого пользователя. Пользователь указывает количество различных пользователей, которые могут использовать этот промокод.", "Max": "Макс.", "Expires at": "Срок действия до", - "Used \/ Uses": "Используется \/ Использует", + "Used / Uses": "Используется / Использует", "Expires": "Истекает", "Sign in to start your session": "Войдите, чтобы начать сессию", "Password": "Пароль", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Ни один узел не был связан!", "No nests available!": "Гнезда в наличии нет!", "No eggs have been linked!": "Группы были связаны!", - "Software \/ Games": "Программное обеспечение \/ Игры", + "Software / Games": "Программное обеспечение / Игры", "Please select software ...": "Пожалуйста, выберите программное обеспечение...", "---": "---", "Specification ": "Характеристики ", @@ -447,6 +447,8 @@ "Notes": "Примечания", "Amount in words": "Сумма прописью", "Please pay until": "Пожалуйста, платите до", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Обновление/Уменьшение вашего сервера сбросит ваш цикл оплаты на текущий. Ваши переплаты будут возвращены. Цена за новый цикл оплаты будет списана", + "Caution": "Внимание", "cs": "Czech", "de": "German", "en": "English", @@ -461,4 +463,4 @@ "ru": "Русский", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/sh.json b/resources/lang/sh.json index 56c63ac36..bdf0019ba 100644 --- a/resources/lang/sh.json +++ b/resources/lang/sh.json @@ -80,7 +80,7 @@ "User ID": "User ID", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -173,7 +173,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -214,9 +214,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -284,7 +284,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -354,7 +354,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -430,6 +430,8 @@ "The Language of the Datatables. Grab the Language-Codes from here": "The Language of the Datatables. Grab the Language-Codes from here", "Let the Client change the Language": "Let the Client change the Language", "Icons updated!": "Icons updated!", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", "cs": "Czech", "de": "German", "en": "English", @@ -442,4 +444,4 @@ "zh": "Chinese", "tr": "Turkish", "ru": "Russian" -} \ No newline at end of file +} diff --git a/resources/lang/sk.json b/resources/lang/sk.json index f002840db..eb2b01e39 100644 --- a/resources/lang/sk.json +++ b/resources/lang/sk.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -93,7 +93,7 @@ "Getting started!": "Getting started!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -388,7 +388,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -447,6 +447,8 @@ "Notes": "Notes", "Amount in words": "Amount in words", "Please pay until": "Please pay until", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizácia alebo deaktualizácia servera resetuje Vašu fakturačnú dobu na aktuálny čas. Vaše nadbytočné kredity budú vrátené. Cena za novú fakturačnú dobu bude odobraná.", + "Caution": "Upozornenie", "cs": "Czech", "de": "German", "en": "English", @@ -461,4 +463,4 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/sr.json b/resources/lang/sr.json index 0c51bf979..d967bb306 100644 --- a/resources/lang/sr.json +++ b/resources/lang/sr.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Greška pri kreiranju servera", "Your servers have been suspended!": "Vaši serveri su suspendovani!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Da biste automatski ponovo omogućili svoje servere, potrebno je da kupite još kredita.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Da biste automatski ponovo omogućili svoje servere, potrebno je da kupite još kredita.", "Purchase credits": "Kupite kredite", "If you have any questions please let us know.": "Ako imate bilo kakvih pitanja, molimo vas da nas obavestite.", "Regards": "Pozdravi", @@ -93,7 +93,7 @@ "Getting started!": "Početak!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Podrazumevani jezik", "The fallback Language, if something goes wrong": "Sekundarni jezik, u slučaju da bude problema", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Automatski prevod", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Korisnički izbor za jezik", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Naplaćuje kredite u vrednosti od prvog sata prilikom kreiranja servera.", "Credits Display Name": "Ime prikaza kredita", "PHPMyAdmin URL": "PHPMyAdmin Link", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Unesite URL adresu instalacije PHPMyAdmin. <strong>Bez kose crte!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Unesite URL adresu instalacije PHPMyAdmin. <strong>Bez kose crte!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Maksimalno", "Expires at": "Ističe", - "Used \/ Uses": "Upotrebljeno \/ Upotrebe", + "Used / Uses": "Upotrebljeno / Upotrebe", "Expires": "Ističe", "Sign in to start your session": "Prijavite se da biste započeli sesiju", "Password": "Lozinka", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Node-ovi nisu povezani!", "No nests available!": "Nema dostupnih gnezda!", "No eggs have been linked!": "Jaja nisu povezana!", - "Software \/ Games": "Softver \/ Igrice", + "Software / Games": "Softver / Igrice", "Please select software ...": "Molimo izaberite softver ...", "---": "---", "Specification ": "Specification ", @@ -447,6 +447,8 @@ "Notes": "Napomena", "Amount in words": "Iznos u rečima", "Please pay until": "Molimo platite do", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", "cs": "Czech", "de": "German", "en": "English", @@ -461,4 +463,4 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/sv.json b/resources/lang/sv.json index f6a377f12..4d84781ae 100644 --- a/resources/lang/sv.json +++ b/resources/lang/sv.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Serverskapande fel", "Your servers have been suspended!": "Ditt konto har blivit avstängt!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "För att automatiskt återaktivera din server\/s, så måste du köpa mer krediter.", + "To automatically re-enable your server/s, you need to purchase more credits.": "För att automatiskt återaktivera din server/s, så måste du köpa mer krediter.", "Purchase credits": "Köp krediter", "If you have any questions please let us know.": "Kontakta oss gärna om du har några eventuella frågor.", "Regards": "Hälsningar", @@ -93,7 +93,7 @@ "Getting started!": "Kom igång!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Förvalt språk", "The fallback Language, if something goes wrong": "Reservspråket, om något går fel", "Datable language": "Daterbart språk", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Datatabellernas språkkod. <br><strong>Exempel:<\/strong> en-gb, fr_fr, de_de<br>Mer information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Datatabellernas språkkod. <br><strong>Exempel:</strong> en-gb, fr_fr, de_de<br>Mer information: ", "Auto-translate": "Auto-översätt", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -388,7 +388,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -447,6 +447,8 @@ "Notes": "Notes", "Amount in words": "Amount in words", "Please pay until": "Please pay until", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", "cs": "Czech", "de": "German", "en": "English", @@ -461,4 +463,4 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish" -} \ No newline at end of file +} diff --git a/resources/lang/tr.json b/resources/lang/tr.json index 385a8dffb..81cf7aab7 100644 --- a/resources/lang/tr.json +++ b/resources/lang/tr.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Birileri senin kodunu kullanarak kayıt oldu!", "Server Creation Error": "Sunucu Oluşturma Hatası", "Your servers have been suspended!": "Sunucularınız askıya alındı!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Sunucularınızı\/sunucularınızı otomatik olarak yeniden etkinleştirmek için daha fazla kredi satın almanız gerekir.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Sunucularınızı/sunucularınızı otomatik olarak yeniden etkinleştirmek için daha fazla kredi satın almanız gerekir.", "Purchase credits": "Satın alma kredisi", "If you have any questions please let us know.": "Herhangi bir sorunuz varsa lütfen bize bildirin.", "Regards": "Saygılarımızla", @@ -93,7 +93,7 @@ "Getting started!": "Başlarken!", "Welcome to our dashboard": "Kontrol panelimize hoş geldiniz", "Verification": "Doğrulama", - "You can verify your e-mail address and link\/verify your Discord account.": "E-posta adresinizi doğrulayabilir ve Discord hesabınızı bağlayabilir\/doğrulayabilirsiniz.", + "You can verify your e-mail address and link/verify your Discord account.": "E-posta adresinizi doğrulayabilir ve Discord hesabınızı bağlayabilir/doğrulayabilirsiniz.", "Information": "Bilgi", "This dashboard can be used to create and delete servers": "Bu gösterge panosu, sunucular oluşturmak ve silmek için kullanılabilir", "These servers can be used and managed on our pterodactyl panel": "Bu sunucular pterodactyl panelimizde kullanılabilir ve yönetilebilir", @@ -187,7 +187,7 @@ "Default language": "Varsayılan Dil", "The fallback Language, if something goes wrong": "Yedek dil, eğer bir şeyler yanlış giderse", "Datable language": "Tarih Dili", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Tarihlerde kullanılacak dil kodu. <br><strong>Örnek:<\/strong> en-gb, fr_fr, de_de<br> Daha fazla bilgi: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Tarihlerde kullanılacak dil kodu. <br><strong>Örnek:</strong> en-gb, fr_fr, de_de<br> Daha fazla bilgi: ", "Auto-translate": "Otomatik çeviri", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Eğer bu seçili ise Yönetim paneli kendisini kullanıcının diline çevirecek, eğer kullanılabiliyorsa", "Client Language-Switch": "Müşteri Dil Değiştiricisi", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Kullanıcı sunucu oluşturduğumda ilk saatin ödemesini direkt olarak alır.", "Credits Display Name": "Kredi ismi", "PHPMyAdmin URL": "PHPMyAdmin linki", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "PHPMyAdmin kurulumunuzun linkini girin <strong> Sonda eğik çizgi olmadan<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "PHPMyAdmin kurulumunuzun linkini girin <strong> Sonda eğik çizgi olmadan</strong>", "Pterodactyl URL": "Pterodactyl Linki", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Pterodactyl kurulumunuzun linkini girin <strong> Sonda eğik çizgi olmadan!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Pterodactyl kurulumunuzun linkini girin <strong> Sonda eğik çizgi olmadan!</strong>", "Pterodactyl API Key": "Pterodactyl API Anahtarı", "Enter the API Key to your Pterodactyl installation.": "Pterodactyl kurulumunuzun API anahtarını girin.", "Force Discord verification": "Discord Doğrulamasını zorunlu yap", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Bir kupon, kullanıcı başına yalnızca bir kez kullanılabilir. Kullanımlar, bu kuponu kullanabilecek farklı kullanıcıların sayısını belirtir.", "Max": "Maks", "Expires at": "Sona eriyor", - "Used \/ Uses": "Kullanılmış \/ Kullanım Alanları", + "Used / Uses": "Kullanılmış / Kullanım Alanları", "Expires": "Sona eriyor", "Sign in to start your session": "Oturumunuzu başlatmak için oturum açın", "Password": "Parola", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Hiçbir makine bağlanmamış!", "No nests available!": "Hiçbir nest bulunamadı!", "No eggs have been linked!": "Hiçbir egg bağlanmamış!", - "Software \/ Games": "Yazılımlar \/ Oyunlar", + "Software / Games": "Yazılımlar / Oyunlar", "Please select software ...": "Lütfen bir yazılım seçin ...", "---": "---", "Specification ": "Özellikler ", @@ -447,6 +447,8 @@ "Notes": "Notlar", "Amount in words": "Yazı ile Tutar", "Please pay until": "Lütfen şu tarihe kadar ödeyin", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Sunucunuzu yükseltmek / düşürmek faturalandırma döngünüzü şimdiye sıfırlayacaktır. Aşırı ödenen kredileriniz iade edilecektir. Yeni faturalandırma döngüsü için ödenen tutar çekilecektir", + "Caution": "Dikkat", "cs": "Çekçe", "de": "Almanca", "en": "İngilizce", @@ -461,4 +463,4 @@ "ru": "Rusça", "sv": "İsveççe", "sk": "Slovakça" -} \ No newline at end of file +} diff --git a/resources/lang/zh.json b/resources/lang/zh.json index efae58c6e..c534a612f 100644 --- a/resources/lang/zh.json +++ b/resources/lang/zh.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "已经有人使用您的代码注册了", "Server Creation Error": "服务器创建错误", "Your servers have been suspended!": "您的服务器已被暂停", - "To automatically re-enable your server\/s, you need to purchase more credits.": "如需重新启用你的服务器,您需要购买更多的余额", + "To automatically re-enable your server/s, you need to purchase more credits.": "如需重新启用你的服务器,您需要购买更多的余额", "Purchase credits": "购买余额", "If you have any questions please let us know.": "如果您有其他任何问题,欢迎联系我们。", "Regards": "此致", @@ -93,7 +93,7 @@ "Getting started!": "开始吧!", "Welcome to our dashboard": "欢迎访问 dashboard", "Verification": "验证", - "You can verify your e-mail address and link\/verify your Discord account.": "你可以验证你的邮箱地址或者连接到你的Discord账户", + "You can verify your e-mail address and link/verify your Discord account.": "你可以验证你的邮箱地址或者连接到你的Discord账户", "Information": "相关信息", "This dashboard can be used to create and delete servers": "此仪表板可用于创建和删除服务器", "These servers can be used and managed on our pterodactyl panel": "这些服务器可以在我们的pterodactyl面板上使用和管理", @@ -187,7 +187,7 @@ "Default language": "默认语言", "The fallback Language, if something goes wrong": "备用语言", "Datable language": "可用语言", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "数据表语言代码 <br><strong>示例:<\/strong> en-gb、fr_fr、de_de<br> 更多信息: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "数据表语言代码 <br><strong>示例:</strong> en-gb、fr_fr、de_de<br> 更多信息: ", "Auto-translate": "自动翻译", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "如果勾选此项,系统将把自己翻译成客户语言(如果有)", "Client Language-Switch": "客户语言切换", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "在创建服务器时收取第一个小时的费用", "Credits Display Name": "积分显示名称", "PHPMyAdmin URL": "PHPMyAdmin地址", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "输入你PHPMyAdmin的URL。<strong>不要有尾部斜线!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "输入你PHPMyAdmin的URL。<strong>不要有尾部斜线!</strong>", "Pterodactyl URL": "Pterodactyl地址", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "输入你Pterodactyl的URL。<strong>不要有尾部斜线!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "输入你Pterodactyl的URL。<strong>不要有尾部斜线!</strong>", "Pterodactyl API Key": "Pterodactyl API密钥", "Enter the API Key to your Pterodactyl installation.": "输入Pterodactyl API密钥", "Force Discord verification": "强制Discord验证", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "每个用户只能使用一次代金券。使用次数指定了可以使用此代金券的不同用户的数量。", "Max": "最大", "Expires at": "过期时间", - "Used \/ Uses": "已使用\/使用情况", + "Used / Uses": "已使用/使用情况", "Expires": "过期", "Sign in to start your session": "登录以开始您的会议", "Password": "密码", @@ -388,7 +388,7 @@ "No nodes have been linked!": "没有节点被链接!", "No nests available!": "没有可用的巢穴!", "No eggs have been linked!": "没有蛋被链接!", - "Software \/ Games": "软件\/游戏", + "Software / Games": "软件/游戏", "Please select software ...": "请选择软件...", "---": "---", "Specification ": "规格 ", @@ -447,6 +447,8 @@ "Notes": "笔记", "Amount in words": "税额的字数", "Please pay until": "请支付至", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "升级/降级你的服务器将重置你的账单周期。你的多余的点数将被退还。新的账单周期的价格将被扣除", + "Caution": "警告", "cs": "捷克语", "de": "德语", "en": "英语", @@ -461,4 +463,4 @@ "ru": "俄语", "sv": "乌克兰语", "sk": "斯洛伐克语" -} \ No newline at end of file +} From 056e41be1a9123b1adef4982f5a2503146dd8e27 Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Mon, 19 Dec 2022 15:08:09 +0100 Subject: [PATCH 047/514] chore: doc --- Addon-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Addon-notes.md b/Addon-notes.md index bed6f5a2e..ac00939be 100644 --- a/Addon-notes.md +++ b/Addon-notes.md @@ -1,4 +1,4 @@ Export diff files: Commit Hash of lates Main commit -git diff -r --no-commit-id --name-only --diff-filter=ACMR \<commit> | tar -czf ../controllpanelgg-monthly-addon/file.tgz -T - +git diff -r --no-commit-id --name-only --diff-filter=ACMR \<commit> | tar -czf \.\./controllpanelgg-monthly-addon/file.tgz -T - From 25db97d0a434bad302076ab33a9e320a7aa250e9 Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Mon, 23 Jan 2023 16:27:32 +0100 Subject: [PATCH 048/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Merge=20missing?= =?UTF-8?q?=20hidden=20product=20in=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/servers/create.blade.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 717e15b8f..b118ac822 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -223,13 +223,17 @@ class="custom-select"> </div> <div class="mt-auto border rounded border-secondary"> <div class="d-flex justify-content-between p-2"> - <span class="d-inline-block mr-4" x-text="'{{ __('Price') }}' + ' (' + product.billing_period + ')'"> + <span class="d-inline-block mr-4" + x-text="'{{ __('Price') }}' + ' (' + product.billing_period + ')'"> </span> <span class="d-inline-block" x-text="product.price + ' {{ CREDITS_DISPLAY_NAME }}'"></span> </div> </div> - <div> + <div> + <input type="hidden" name="product" x-model="selectedProduct"> + </div> + <div> <button type="submit" x-model="selectedProduct" name="product" :disabled="product.minimum_credits > user.credits || product.doesNotFit == true || product.price > user.credits || submitClicked" @@ -240,14 +244,14 @@ class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" </button> </div> + </div> </div> + </template> </div> - </template> </div> - </div> - </form> - <!-- END FORM --> + </form> + <!-- END FORM --> </div> </section> From 54325fbf3ea643c415730a2590df2247124a44bf Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:32:00 +0200 Subject: [PATCH 049/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Migration?= =?UTF-8?q?=20->=20Column=20Billing=20Period?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._092704_add_billing_period_to_products.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 database/migrations/2022_06_16_092704_add_billing_period_to_products.php diff --git a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php new file mode 100644 index 000000000..4018fb473 --- /dev/null +++ b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php @@ -0,0 +1,35 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; + +class AddBillingPeriodToProducts extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('products', function (Blueprint $table) { + $table->string('billing_period')->default("hourly"); + }); + + DB::statement('UPDATE products SET billing_period="hourly"'); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('products', function (Blueprint $table) { + $table->dropColumn('billing_period'); + }); + } +} From 88a85416232e6f0c2a13318cadfddd2c3137fdd4 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:55:34 +0200 Subject: [PATCH 050/514] =?UTF-8?q?fix:=20=E2=9C=A8=20Updated=20Migration?= =?UTF-8?q?=20to=20calculate=20hourly=20price=20from=20existing=20products?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2022_06_16_092704_add_billing_period_to_products.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php index 4018fb473..708e6e5e4 100644 --- a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php +++ b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php @@ -19,6 +19,14 @@ public function up() }); DB::statement('UPDATE products SET billing_period="hourly"'); + + $products = DB::table('products')->get(); + foreach ($products as $product) { + $price = $product->price; + $price = $price / 30 / 24; + DB::table('products')->where('id', $product->id)->update(['price' => $price]); + } + } /** From 23cc70d79ce3fa8147c99c2b5b096f56a5b75413 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:56:05 +0200 Subject: [PATCH 051/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Added=20Migration?= =?UTF-8?q?=20->=20Updated=20user=20credits=20column=20to=20be=20decimal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6_16_094402_update_user_price_datatype.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2022_06_16_094402_update_user_price_datatype.php diff --git a/database/migrations/2022_06_16_094402_update_user_price_datatype.php b/database/migrations/2022_06_16_094402_update_user_price_datatype.php new file mode 100644 index 000000000..9b4abb9e7 --- /dev/null +++ b/database/migrations/2022_06_16_094402_update_user_price_datatype.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class UpdateUserPriceDatatype extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('users', function (Blueprint $table) { + $table->decimal('credits', 20, 10)->default(0)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->unsignedFloat('credits')->default(250)->change(); + }); + } +} From 00e525a7645f550a61898c3c2165983284eb627c Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 11:59:17 +0200 Subject: [PATCH 052/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Migration?= =?UTF-8?q?=20->=20last=20billed=20field=20to=20servers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...95818_add_last_billed_field_to_servers.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php diff --git a/database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php b/database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php new file mode 100644 index 000000000..6b05f3a5b --- /dev/null +++ b/database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php @@ -0,0 +1,33 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; + +class AddLastBilledFieldToServers extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('servers', function (Blueprint $table) { + $table->dateTime('last_billed')->default(DB::raw('CURRENT_TIMESTAMP'))->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('last_billed'); + }); + } +} From 195cadc6a5f5ec9668913d96c425b9eac50bf50d Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 12:01:53 +0200 Subject: [PATCH 053/514] =?UTF-8?q?feat:=20=F0=9F=93=9D=20Updated=20Produc?= =?UTF-8?q?tSeeder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/seeders/Seeds/ProductSeeder.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/seeders/Seeds/ProductSeeder.php b/database/seeders/Seeds/ProductSeeder.php index 4f5e43754..dc26a9a1e 100644 --- a/database/seeders/Seeds/ProductSeeder.php +++ b/database/seeders/Seeds/ProductSeeder.php @@ -16,7 +16,7 @@ public function run() { Product::create([ 'name' => 'Starter', - 'description' => '64MB Ram, 1GB Disk, 1 Database, 140 credits monthly', + 'description' => '64MB Ram, 1GB Disk, 1 Database, 140 credits hourly', 'price' => 140, 'memory' => 64, 'disk' => 1000, @@ -25,7 +25,7 @@ public function run() Product::create([ 'name' => 'Standard', - 'description' => '128MB Ram, 2GB Disk, 2 Database, 210 credits monthly', + 'description' => '128MB Ram, 2GB Disk, 2 Database, 210 credits hourly', 'price' => 210, 'memory' => 128, 'disk' => 2000, @@ -34,7 +34,7 @@ public function run() Product::create([ 'name' => 'Advanced', - 'description' => '256MB Ram, 5GB Disk, 5 Database, 280 credits monthly', + 'description' => '256MB Ram, 5GB Disk, 5 Database, 280 credits hourly', 'price' => 280, 'memory' => 256, 'disk' => 5000, From 9777e22eab4562b3bc7097e0ed7a63111f39646c Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 12:04:52 +0200 Subject: [PATCH 054/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20billing=5Fp?= =?UTF-8?q?eriod=20to=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Admin/ProductController.php | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 5e9157d9f..53b1c7de3 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -57,21 +57,22 @@ public function clone(Request $request, Product $product) public function store(Request $request) { $request->validate([ - 'name' => 'required|max:30', - 'price' => 'required|numeric|max:1000000|min:0', - 'memory' => 'required|numeric|max:1000000|min:5', - 'cpu' => 'required|numeric|max:1000000|min:0', - 'swap' => 'required|numeric|max:1000000|min:0', - 'description' => 'required|string|max:191', - 'disk' => 'required|numeric|max:1000000|min:5', - 'minimum_credits' => 'required|numeric|max:1000000|min:-1', - 'io' => 'required|numeric|max:1000000|min:0', - 'databases' => 'required|numeric|max:1000000|min:0', - 'backups' => 'required|numeric|max:1000000|min:0', - 'allocations' => 'required|numeric|max:1000000|min:0', - 'nodes.*' => 'required|exists:nodes,id', - 'eggs.*' => 'required|exists:eggs,id', - 'disabled' => 'nullable', + "name" => "required|max:30", + "price" => "required|numeric|max:1000000|min:0", + "memory" => "required|numeric|max:1000000|min:5", + "cpu" => "required|numeric|max:1000000|min:0", + "swap" => "required|numeric|max:1000000|min:0", + "description" => "required|string|max:191", + "disk" => "required|numeric|max:1000000|min:5", + "minimum_credits" => "required|numeric|max:1000000|min:-1", + "io" => "required|numeric|max:1000000|min:0", + "databases" => "required|numeric|max:1000000|min:0", + "backups" => "required|numeric|max:1000000|min:0", + "allocations" => "required|numeric|max:1000000|min:0", + "nodes.*" => "required|exists:nodes,id", + "eggs.*" => "required|exists:eggs,id", + "disabled" => "nullable", + "billing_period" => "required|in:hourly,daily,monthly", ]); $disabled = ! is_null($request->input('disabled')); @@ -123,21 +124,22 @@ public function edit(Product $product) public function update(Request $request, Product $product): RedirectResponse { $request->validate([ - 'name' => 'required|max:30', - 'price' => 'required|numeric|max:1000000|min:0', - 'memory' => 'required|numeric|max:1000000|min:5', - 'cpu' => 'required|numeric|max:1000000|min:0', - 'swap' => 'required|numeric|max:1000000|min:0', - 'description' => 'required|string|max:191', - 'disk' => 'required|numeric|max:1000000|min:5', - 'io' => 'required|numeric|max:1000000|min:0', - 'minimum_credits' => 'required|numeric|max:1000000|min:-1', - 'databases' => 'required|numeric|max:1000000|min:0', - 'backups' => 'required|numeric|max:1000000|min:0', - 'allocations' => 'required|numeric|max:1000000|min:0', - 'nodes.*' => 'required|exists:nodes,id', - 'eggs.*' => 'required|exists:eggs,id', - 'disabled' => 'nullable', + "name" => "required|max:30", + "price" => "required|numeric|max:1000000|min:0", + "memory" => "required|numeric|max:1000000|min:5", + "cpu" => "required|numeric|max:1000000|min:0", + "swap" => "required|numeric|max:1000000|min:0", + "description" => "required|string|max:191", + "disk" => "required|numeric|max:1000000|min:5", + "io" => "required|numeric|max:1000000|min:0", + "minimum_credits" => "required|numeric|max:1000000|min:-1", + "databases" => "required|numeric|max:1000000|min:0", + "backups" => "required|numeric|max:1000000|min:0", + "allocations" => "required|numeric|max:1000000|min:0", + "nodes.*" => "required|exists:nodes,id", + "eggs.*" => "required|exists:eggs,id", + "disabled" => "nullable", + "billing_period" => "required|in:hourly,daily,monthly", ]); $disabled = ! is_null($request->input('disabled')); From 7dab9d3c7e66d319ee5b209a5bddb3f8e4ca80dd Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 12:27:53 +0200 Subject: [PATCH 055/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20billing=20p?= =?UTF-8?q?eriod=20to=20edit,=20create=20and=20show=20of=20products?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/admin/products/create.blade.php | 55 ++++++++++++++----- .../views/admin/products/edit.blade.php | 54 +++++++++++++----- .../views/admin/products/index.blade.php | 2 + 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index c6d72f68b..d7257fcf5 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -116,6 +116,20 @@ class="form-control @error('swap') is-invalid @enderror" @enderror </div> + <div class="form-group"> + <label for="allocations">{{__('Allocations')}}</label> + <input value="{{$product->allocations ?? old('allocations') ?? 0}}" + id="allocations" name="allocations" + type="number" + class="form-control @error('allocations') is-invalid @enderror" + required="required"> + @error('allocations') + <div class="invalid-feedback"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group"> <label for="description">{{__('Description')}} <i data-toggle="popover" data-trigger="hover" @@ -148,6 +162,34 @@ class="form-control @error('disk') is-invalid @enderror" @enderror </div> + <div class="form-group"> + <label for="billing_period">{{__('Billing Period')}} <i + data-toggle="popover" data-trigger="hover" + data-content="{{__('Period when the user will be charged for the given price')}}" + class="fas fa-info-circle"></i></label> + + <select id="billing_period" style="width:100%" class="custom-select" name="billing_period" required + autocomplete="off" @error('billing_period') is-invalid @enderror> + <option value="hourly" @if ($product->billing_period == 'hourly') selected + @endif> + {{__('Hourly')}} + </option> + <option value="daily" @if ($product->billing_period == 'daily') selected + @endif> + {{__('Daily')}} + </option> + <option value="monthly" @if ($product->billing_period == 'monthly') selected + @endif> + {{__('Monthly')}} + </option> + </select> + @error('billing_period') + <div class="invalid-feedback"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group"> <label for="minimum_credits">{{__('Minimum')}} {{ CREDITS_DISPLAY_NAME }} <i data-toggle="popover" data-trigger="hover" @@ -205,19 +247,6 @@ class="form-control @error('backups') is-invalid @enderror" </div> @enderror </div> - <div class="form-group"> - <label for="allocations">{{__('Allocations')}}</label> - <input value="{{$product->allocations ?? old('allocations') ?? 0}}" - id="allocations" name="allocations" - type="number" - class="form-control @error('allocations') is-invalid @enderror" - required="required"> - @error('allocations') - <div class="invalid-feedback"> - {{ $message }} - </div> - @enderror - </div> </div> </div> diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index 61357c716..f23533bf0 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -122,7 +122,18 @@ class="form-control @error('swap') is-invalid @enderror" </div> @enderror </div> - + <div class="form-group"> + <label for="allocations">{{__('Allocations')}}</label> + <input value="{{ $product->allocations }}" id="allocations" + name="allocations" type="number" + class="form-control @error('allocations') is-invalid @enderror" + required="required"> + @error('allocations') + <div class="invalid-feedback"> + {{ $message }} + </div> + @enderror + </div> <div class="form-group"> <label for="description">{{__('Description')}} <i data-toggle="popover" data-trigger="hover" @@ -152,6 +163,35 @@ class="form-control @error('disk') is-invalid @enderror" </div> @enderror </div> + + <div class="form-group"> + <label for="billing_period">{{__('Billing Period')}} <i + data-toggle="popover" data-trigger="hover" + data-content="{{__('Period when the user will be charged for the given price')}}" + class="fas fa-info-circle"></i></label> + + <select id="billing_period" style="width:100%" class="custom-select" name="billing_period" required + autocomplete="off" @error('billing_period') is-invalid @enderror> + <option value="hourly" @if ($product->billing_period == 'hourly') selected + @endif> + {{__('Hourly')}} + </option> + <option value="daily" @if ($product->billing_period == 'daily') selected + @endif> + {{__('Daily')}} + </option> + <option value="monthly" @if ($product->billing_period == 'monthly') selected + @endif> + {{__('Monthly')}} + </option> + </select> + @error('billing_period') + <div class="invalid-feedback"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group"> <label for="minimum_credits">{{__('Minimum')}} {{ CREDITS_DISPLAY_NAME }} <i data-toggle="popover" data-trigger="hover" @@ -202,18 +242,6 @@ class="form-control @error('backups') is-invalid @enderror" </div> @enderror </div> - <div class="form-group"> - <label for="allocations">{{__('Allocations')}}</label> - <input value="{{ $product->allocations }}" id="allocations" - name="allocations" type="number" - class="form-control @error('allocations') is-invalid @enderror" - required="required"> - @error('allocations') - <div class="invalid-feedback"> - {{ $message }} - </div> - @enderror - </div> </div> </div> diff --git a/themes/default/views/admin/products/index.blade.php b/themes/default/views/admin/products/index.blade.php index bc15a70b4..729e2d5c4 100644 --- a/themes/default/views/admin/products/index.blade.php +++ b/themes/default/views/admin/products/index.blade.php @@ -44,6 +44,7 @@ class="fas fa-plus mr-1"></i>{{__('Create new')}}</a> <th>{{__('Active')}}</th> <th>{{__('Name')}}</th> <th>{{__('Price')}}</th> + <th>{{__('Billing period')}}</th> <th>{{__('Memory')}}</th> <th>{{__('Cpu')}}</th> <th>{{__('Swap')}}</th> @@ -92,6 +93,7 @@ function submitResult() { {data: "disabled"}, {data: "name"}, {data: "price"}, + {data: "billing_period"}, {data: "memory"}, {data: "cpu"}, {data: "swap"}, From f9d5238ea28c02d8ee3a92c93555b10fe02bf4af Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 12:36:01 +0200 Subject: [PATCH 056/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20billing=20p?= =?UTF-8?q?eriod=20to=20server=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/servers/create.blade.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index a613c29f4..b184d4626 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -208,9 +208,14 @@ class="custom-select"> <span class="d-inline-block" x-text="product.allocations"></span> </li> <li class="d-flex justify-content-between"> + <span class="d-inline-block"><i class="fas fa-clock"></i> + {{ __('Billing Period') }}</span> + + <span class="d-inline-block" x-text="product.billing_period"></span> + </li> + <li> <span class="d-inline-block"><i class="fa fa-coins"></i> - {{ __('Required') }} {{ CREDITS_DISPLAY_NAME }} - {{ __('to create this server') }}</span> + {{ __('Minimum') }} {{ CREDITS_DISPLAY_NAME }}</span> <span class="d-inline-block" x-text="product.minimum_credits == -1 ? {{ config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER') }} : product.minimum_credits"></span> </li> @@ -224,8 +229,7 @@ class="custom-select"> </div> <div class="mt-auto border rounded border-secondary"> <div class="d-flex justify-content-between p-2"> - <span class="d-inline-block mr-4"> - {{ __('Price') }}: + <span class="d-inline-block mr-4" x-text="'{{ __('Price') }}' + ' (' + product.billing_period + ')'"> </span> <span class="d-inline-block" x-text="product.price + ' {{ CREDITS_DISPLAY_NAME }}'"></span> From 681928c3ad4ddb19a74aff2c260d2055c7982c9a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 14:54:06 +0200 Subject: [PATCH 057/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20weekly=20to?= =?UTF-8?q?=20billing=5Fperiod=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Admin/ProductController.php | 4 ++-- themes/default/views/admin/products/create.blade.php | 4 ++++ themes/default/views/admin/products/edit.blade.php | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 53b1c7de3..2893a262a 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -72,7 +72,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,monthly", + "billing_period" => "required|in:hourly,daily,weekly,monthly", ]); $disabled = ! is_null($request->input('disabled')); @@ -139,7 +139,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,monthly", + "billing_period" => "required|in:hourly,daily,weekly,monthly", ]); $disabled = ! is_null($request->input('disabled')); diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index d7257fcf5..a4b477967 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -177,6 +177,10 @@ class="fas fa-info-circle"></i></label> <option value="daily" @if ($product->billing_period == 'daily') selected @endif> {{__('Daily')}} + </option> + <option value="weekly" @if ($product->billing_period == 'weekly') selected + @endif> + {{__('Weekly')}} </option> <option value="monthly" @if ($product->billing_period == 'monthly') selected @endif> diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index f23533bf0..2de2e7df6 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -179,6 +179,10 @@ class="fas fa-info-circle"></i></label> <option value="daily" @if ($product->billing_period == 'daily') selected @endif> {{__('Daily')}} + </option> + <option value="weekly" @if ($product->billing_period == 'weekly') selected + @endif> + {{__('Weekly')}} </option> <option value="monthly" @if ($product->billing_period == 'monthly') selected @endif> From 7e17bb62ea02b2728c2cca0fe4b6b13b4e233671 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 14:54:42 +0200 Subject: [PATCH 058/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20ChargeServe?= =?UTF-8?q?rs=20command=20&=20updated=20laravel=20schedule=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 122 +++++++++++++++++++++++++ app/Console/Kernel.php | 12 ++- 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 app/Console/Commands/ChargeServers.php diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php new file mode 100644 index 000000000..678ab4b70 --- /dev/null +++ b/app/Console/Commands/ChargeServers.php @@ -0,0 +1,122 @@ +<?php + +namespace App\Console\Commands; + +use App\Models\Server; +use App\Notifications\ServersSuspendedNotification; +use Illuminate\Console\Command; + +class ChargeServers extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'servers:charge'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Charge all users with severs that are due to be charged'; + + /** + * A list of users that have to be notified + * @var array + */ + protected $usersToNotify = []; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + Server::whereNull('suspended')->with('users', 'products')->chunk(10, function ($servers) { + /** @var Server $server */ + foreach ($servers as $server) { + /** @var Product $product */ + $product = $server->product; + /** @var User $user */ + $user = $server->user; + + $billing_period = $product->billing_period; + + // check if server is due to be charged by comparing its last_billed date with the current date and the billing period + $newBillingDate = null; + switch($billing_period) { + case 'monthly': + $newBillingDate = $server->last_billed->addMonth(); + break; + case 'weekly': + $newBillingDate = $server->last_billed->addYear(); + break; + case 'daily': + $newBillingDate = $server->last_billed->addDay(); + break; + default: + $newBillingDate = $server->last_billed->addHour(); + break; + }; + if (!($newBillingDate <= now())) return; + + // check if user has enough credits to charge the server + if ($user->credits < $product->price) { + try { + #suspend server + $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); + $server->suspend(); + + #add user to notify list + if (!in_array($user, $this->usersToNotify)) { + array_push($this->usersToNotify, $user); + } + } catch (\Exception $exception) { + $this->error($exception->getMessage()); + } + return; + } + + // charge credits to user + $this->line("<fg=blue>{$user->name}</> Current credits: <fg=green>{$user->credits}</> Credits to be removed: <fg=red>{$product->price}</>"); + $user->decrement('credits', $product->price); + + // update server last_billed date + $server->last_billed = $newBillingDate; + } + + return $this->notifyUsers(); + }); + } + + /** + * @return bool + */ + public function notifyUsers() + { + if (!empty($this->usersToNotify)) { + /** @var User $user */ + foreach ($this->usersToNotify as $user) { + $this->line("<fg=yellow>Notified user:</> <fg=blue>{$user->name}</>"); + $user->notify(new ServersSuspendedNotification()); + } + } + + #reset array + $this->usersToNotify = array(); + return true; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 28d1d36ae..64af565f9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -8,6 +8,16 @@ class Kernel extends ConsoleKernel { + /** + * The Artisan commands provided by your application. + * + * @var array + */ + protected $commands = [ + Commands\ChargeCreditsCommand::class, + Commands\ChargeServers::class, + ]; + /** * Define the application's command schedule. * @@ -16,7 +26,7 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command('credits:charge')->hourly(); + $schedule->command('servers:charge')->everyMinute(); $schedule->command('cp:versioncheck:get')->daily(); $schedule->command('payments:open:clear')->daily(); From 3723b527f52eb949ffed1bfefd2d3d3983880fbc Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 15:32:42 +0200 Subject: [PATCH 059/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20decimal?= =?UTF-8?q?=20input=20steps=20to=20number=20inputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/admin/products/create.blade.php | 1 + themes/default/views/admin/products/edit.blade.php | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index a4b477967..913fda1f0 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -66,6 +66,7 @@ class="form-control @error('name') is-invalid @enderror" <label for="price">{{__('Price in')}} {{CREDITS_DISPLAY_NAME}}</label> <input value="{{$product->price ?? old('price')}}" id="price" name="price" step=".01" type="number" + step="0.0001" class="form-control @error('price') is-invalid @enderror" required="required"> @error('price') diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index 2de2e7df6..cedbc72c8 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -75,8 +75,10 @@ class="form-control @error('name') is-invalid @enderror" </div> <div class="form-group"> - <label for="price">{{__('Price in')}} {{ CREDITS_DISPLAY_NAME }}</label> - <input value="{{ $product->price }}" id="price" name="price" type="number" step=".01" + <label for="price">{{__('Price in')}} {{CREDITS_DISPLAY_NAME}}</label> + <input value="{{$product->price}}" id="price" name="price" + type="number" + step="0.0001" class="form-control @error('price') is-invalid @enderror" required="required"> @error('price') From 5b738be6e142afd1190d2f610221db398af0fd77 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 15:33:44 +0200 Subject: [PATCH 060/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Renamed=20Migrati?= =?UTF-8?q?on=20&=20changed=20precision=20on=20decimals=20to=20more=20reas?= =?UTF-8?q?onable=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2022_06_16_092704_add_billing_period_to_products.php | 5 +++++ ...hp => 2022_06_16_094402_update_user_credits_datatype.php} | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) rename database/migrations/{2022_06_16_094402_update_user_price_datatype.php => 2022_06_16_094402_update_user_credits_datatype.php} (82%) diff --git a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php index 708e6e5e4..ceed9362f 100644 --- a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php +++ b/database/migrations/2022_06_16_092704_add_billing_period_to_products.php @@ -16,6 +16,9 @@ public function up() { Schema::table('products', function (Blueprint $table) { $table->string('billing_period')->default("hourly"); + $table->decimal('price', 15, 4)->change(); + $table->decimal('minimum_credits', 15, 4)->change(); + }); DB::statement('UPDATE products SET billing_period="hourly"'); @@ -38,6 +41,8 @@ public function down() { Schema::table('products', function (Blueprint $table) { $table->dropColumn('billing_period'); + $table->decimal('price', 10, 0)->change(); + $table->float('minimum_credits')->change(); }); } } diff --git a/database/migrations/2022_06_16_094402_update_user_price_datatype.php b/database/migrations/2022_06_16_094402_update_user_credits_datatype.php similarity index 82% rename from database/migrations/2022_06_16_094402_update_user_price_datatype.php rename to database/migrations/2022_06_16_094402_update_user_credits_datatype.php index 9b4abb9e7..ed5922e86 100644 --- a/database/migrations/2022_06_16_094402_update_user_price_datatype.php +++ b/database/migrations/2022_06_16_094402_update_user_credits_datatype.php @@ -4,7 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class UpdateUserPriceDatatype extends Migration +class UpdateUserCreditsDatatype extends Migration { /** * Run the migrations. @@ -14,7 +14,7 @@ class UpdateUserPriceDatatype extends Migration public function up() { Schema::table('users', function (Blueprint $table) { - $table->decimal('credits', 20, 10)->default(0)->change(); + $table->decimal('credits', 15, 4)->default(0)->change(); }); } @@ -27,6 +27,7 @@ public function down() { Schema::table('users', function (Blueprint $table) { $table->unsignedFloat('credits')->default(250)->change(); + }); } } From da0dd37559a9ba985def73ab677ffb1e97c51d7c Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 16:15:57 +0200 Subject: [PATCH 061/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20last=5Fbill?= =?UTF-8?q?ed=20to=20server=20model=20&=20always=20charge=20first=20&=20fi?= =?UTF-8?q?xed=20Charge=20Server=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 24 +++++++++++++++-------- app/Http/Controllers/ServerController.php | 9 ++++----- app/Models/Server.php | 13 ++++++------ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 678ab4b70..877cb8a5a 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -4,7 +4,9 @@ use App\Models\Server; use App\Notifications\ServersSuspendedNotification; +use Carbon\Carbon; use Illuminate\Console\Command; +use Illuminate\Support\Facades\DB; class ChargeServers extends Command { @@ -45,7 +47,7 @@ public function __construct() */ public function handle() { - Server::whereNull('suspended')->with('users', 'products')->chunk(10, function ($servers) { + Server::whereNull('suspended')->with('user', 'product')->chunk(10, function ($servers) { /** @var Server $server */ foreach ($servers as $server) { /** @var Product $product */ @@ -55,23 +57,29 @@ public function handle() $billing_period = $product->billing_period; + // check if server is due to be charged by comparing its last_billed date with the current date and the billing period $newBillingDate = null; switch($billing_period) { case 'monthly': - $newBillingDate = $server->last_billed->addMonth(); + $newBillingDate = Carbon::parse($server->last_billed)->addMonth(); break; case 'weekly': - $newBillingDate = $server->last_billed->addYear(); + $newBillingDate = Carbon::parse($server->last_billed)->addYear(); break; case 'daily': - $newBillingDate = $server->last_billed->addDay(); + $newBillingDate = Carbon::parse($server->last_billed)->addDay(); break; + case 'hourly': + $newBillingDate = Carbon::parse($server->last_billed)->addHour(); default: - $newBillingDate = $server->last_billed->addHour(); + $newBillingDate = Carbon::parse($server->last_billed)->addHour(); break; }; - if (!($newBillingDate <= now())) return; + + if (!($newBillingDate->isPast())) { + continue; + } // check if user has enough credits to charge the server if ($user->credits < $product->price) { @@ -94,8 +102,8 @@ public function handle() $this->line("<fg=blue>{$user->name}</> Current credits: <fg=green>{$user->credits}</> Credits to be removed: <fg=red>{$product->price}</>"); $user->decrement('credits', $product->price); - // update server last_billed date - $server->last_billed = $newBillingDate; + // update server last_billed date in db + DB::table('servers')->where('id', $server->id)->update(['last_billed' => $newBillingDate]); } return $this->notifyUsers(); diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 656064c39..b066e77d6 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -10,6 +10,7 @@ use App\Models\Product; use App\Models\Server; use App\Notifications\ServerCreationError; +use Carbon\Carbon; use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Client\Response; @@ -180,6 +181,7 @@ public function store(Request $request) $server = $request->user()->servers()->create([ 'name' => $request->input('name'), 'product_id' => $request->input('product'), + 'last_billed' => Carbon::now()->toDateTimeString(), ]); //get free allocation ID @@ -201,11 +203,8 @@ public function store(Request $request) 'identifier' => $serverAttributes['identifier'], ]); - if (config('SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR', 'true') == 'true') { - if ($request->user()->credits >= $server->product->getHourlyPrice()) { - $request->user()->decrement('credits', $server->product->getHourlyPrice()); - } - } + // Charge first billing cycle + $request->user()->decrement('credits', $server->product->price); return redirect()->route('servers.index')->with('success', __('Server created')); } diff --git a/app/Models/Server.php b/app/Models/Server.php index 94365dd47..cf71f040e 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -47,12 +47,13 @@ public function getActivitylogOptions(): LogOptions * @var string[] */ protected $fillable = [ - 'name', - 'description', - 'suspended', - 'identifier', - 'product_id', - 'pterodactyl_id', + "name", + "description", + "suspended", + "identifier", + "product_id", + "pterodactyl_id", + "last_billed" ]; /** From 2e6c03e3cccfa1b93ed65c0ab814fde442ee6dcc Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 16:18:27 +0200 Subject: [PATCH 062/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Update=20last=5Fbil?= =?UTF-8?q?led=20to=20current=20time=20on=20unsuspend=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Server.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Models/Server.php b/app/Models/Server.php index cf71f040e..c3ea621a3 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Classes\Pterodactyl; +use Carbon\Carbon; use Exception; use GuzzleHttp\Promise\PromiseInterface; use Hidehalo\Nanoid\Client; @@ -126,9 +127,11 @@ public function unSuspend() if ($response->successful()) { $this->update([ 'suspended' => null, + 'last_billed' => Carbon::now()->toDateTimeString(), ]); } + return $this; } From 907fb747347ab7ebe980ede1361bc04c651db5ae Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 16 Jun 2022 19:03:59 +0200 Subject: [PATCH 063/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20ChargeSe?= =?UTF-8?q?rver=20Command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 877cb8a5a..769909c32 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -65,7 +65,7 @@ public function handle() $newBillingDate = Carbon::parse($server->last_billed)->addMonth(); break; case 'weekly': - $newBillingDate = Carbon::parse($server->last_billed)->addYear(); + $newBillingDate = Carbon::parse($server->last_billed)->addWeek(); break; case 'daily': $newBillingDate = Carbon::parse($server->last_billed)->addDay(); From 117b75d3b75d21c88639030c2e175184ad25a414 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Sat, 18 Jun 2022 23:41:40 +0200 Subject: [PATCH 064/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20different?= =?UTF-8?q?=20billing=20periods=20to=20servers=20overview=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 208 ++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 resources/views/servers/index.blade.php diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php new file mode 100644 index 000000000..517c3f1ea --- /dev/null +++ b/resources/views/servers/index.blade.php @@ -0,0 +1,208 @@ +@extends('layouts.main') + +@section('content') + <!-- CONTENT HEADER --> + <section class="content-header"> + <div class="container-fluid"> + <div class="row mb-2"> + <div class="col-sm-6"> + <h1>{{ __('Servers') }}</h1> + </div> + <div class="col-sm-6"> + <ol class="breadcrumb float-sm-right"> + <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li> + <li class="breadcrumb-item"><a class="text-muted" + href="{{ route('servers.index') }}">{{ __('Servers') }}</a> + </li> + </ol> + </div> + </div> + </div> + </section> + <!-- END CONTENT HEADER --> + + <!-- MAIN CONTENT --> + <section class="content"> + <div class="container-fluid"> + + <!-- CUSTOM CONTENT --> + <div class="d-flex justify-content-md-start justify-content-center mb-3 "> + <a @if (Auth::user()->Servers->count() >= Auth::user()->server_limit) + disabled="disabled" title="Server limit reached!" + @endif href="{{ route('servers.create') }}" + class="btn + @if (Auth::user()->Servers->count() >= Auth::user()->server_limit) disabled + @endif btn-primary"><i + class="fa fa-plus mr-2"></i> + {{ __('Create Server') }} + </a> + </div> + + <div class="row d-flex flex-row justify-content-center justify-content-md-start"> + @foreach ($servers as $server) + + <div class="col-xl-3 col-lg-5 col-md-6 col-sm-6 col-xs-12 card pr-0 pl-0 ml-sm-2 mr-sm-3" + style="max-width: 350px"> + <div class="card-header"> + <div class="d-flex justify-content-between align-items-center"> + <h5 class="card-title mt-1">{{ $server->name }} + </h5> + <div class="card-tools mt-1"> + <div class="dropdown no-arrow"> + <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" + aria-haspopup="true" aria-expanded="false"> + <i class="fas fa-ellipsis-v fa-sm fa-fw text-white-50"></i> + </a> + <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" + aria-labelledby="dropdownMenuLink"> + @if (!empty(config('SETTINGS::MISC:PHPMYADMIN:URL'))) + <a href="{{ config('SETTINGS::MISC:PHPMYADMIN:URL') }}" + class="dropdown-item text-info" target="__blank"><i title="manage" + class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span></a> + @endif + <div class="dropdown-divider"></div> + <span class="dropdown-item"><i title="Created at" + class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') }}</span></span> + </div> + </div> + </div> + </div> + </div> + <div class="card-body"> + <div class="container mt-1"> + <div class="row mb-3"> + <div class="col my-auto">{{ __('Status') }}:</div> + <div class="col-7 my-auto"> + <i + class="fas {{ $server->isSuspended() ? 'text-danger' : 'text-success' }} fa-circle mr-2"></i> + {{ $server->isSuspended() ? 'Suspended' : 'Active' }} + </div> + </div> + <div class="row mb-2"> + <div class="col-5"> + {{ __('Location') }}: + </div> + <div class="col-7 d-flex justify-content-between align-items-center"> + <span class="">{{ $server->location }}</span> + <i data-toggle="popover" data-trigger="hover" + data-content="{{ __('Node') }}: {{ $server->node }}" + class="fas fa-info-circle"></i> + </div> + + </div> + <div class="row mb-2"> + <div class="col-5 "> + {{ __('Software') }}: + </div> + <div class="col-7 text-wrap"> + <span>{{ $server->nest }}</span> + </div> + + </div> + <div class="row mb-2"> + <div class="col-5 "> + {{ __('Specification') }}: + </div> + <div class="col-7 text-wrap"> + <span>{{ $server->egg }}</span> + </div> + </div> + <div class="row mb-4"> + <div class="col-5 "> + {{ __('Resource plan') }}: + </div> + <div class="col-7 text-wrap d-flex justify-content-between align-items-center"> + <span>{{ $server->product->name }} + </span> + <i data-toggle="popover" data-trigger="hover" data-html="true" + data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/>" + class="fas fa-info-circle"></i> + </div> + + </div> + <div class="row mb-2"> + <div class="col-4"> + {{ __('Price') }}: + <span class="text-muted"> + ({{ CREDITS_DISPLAY_NAME }}) + </span> + </div> + <div class="col-8 text-center"> + <div class="text-muted"> + @if($server->product->billing_period == 'monthly') + {{ __('per Month') }} + @elseif($server->product->billing_period == 'weekly') + {{ __('per Week') }} + @elseif($server->product->billing_period == 'daily') + {{ __('per Day') }} + @elseif($server->product->billing_period == 'hourly') + {{ __('per Hour') }} + @endif + </div> + <span> + {{ $server->product->price }} + </span> + </div> + </div> + </div> + </div> + + <div class="card-footer d-flex align-items-center justify-content-between"> + <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" + target="__blank" + class="btn btn-info mx-3 w-100 align-items-center justify-content-center d-flex"> + <i class="fas fa-tools mr-2"></i> + <span>{{ __('Manage') }}</span> + </a> + <button onclick="confirmSubmit('{{ $server->id }}', handleServerDelete);" target="__blank" + class="btn btn-danger mx-3 w-100 align-items-center justify-content-center d-flex"> + <i class="fas fa-trash mr-2"></i> + <span>{{ __('Delete') }}</span> + </button> + </div> + </div> + @endforeach + </div> + <!-- END CUSTOM CONTENT --> + </div> + </section> + <!-- END CONTENT --> + + <script> + const confirmSubmit = (serverId, handleServerDelete) => { + // Confirm delete submit with sweetalert + Swal.fire({ + title: "{{ __('Are you sure?') }}", + text: "{{ __('This is an irreversible action, all files of this server will be removed.') }}", + icon: 'warning', + confirmButtonColor: '#d9534f', + showCancelButton: true, + confirmButtonText: "{{ __('Yes, delete it!') }}", + cancelButtonText: "{{ __('No, cancel!') }}", + reverseButtons: true + }).then((result) => { + if (result.value) { + handleServerDelete(serverId); + return + } + Swal.fire("{{ __('Canceled ...') }}", `{{ __('Deletion has been canceled.') }}`, 'info'); + }); + } + + const handleServerDelete = (serverId) => { + // Delete server + fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + window.location.reload(); + }); + } + + document.addEventListener('DOMContentLoaded', () => { + $('[data-toggle="popover"]').popover(); + }); + </script> +@endsection From b1fc1f8fab496a867b2dde5e01f812a059db7e11 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Sat, 18 Jun 2022 23:55:43 +0200 Subject: [PATCH 065/514] =?UTF-8?q?docs:=20=F0=9F=93=9D=20Added=20Addon=20?= =?UTF-8?q?Docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Addon-notes.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Addon-notes.md diff --git a/Addon-notes.md b/Addon-notes.md new file mode 100644 index 000000000..3c335a7f6 --- /dev/null +++ b/Addon-notes.md @@ -0,0 +1,3 @@ +Export diff files: + +cp -pv --parents $(git diff <commit> --name-only) "..\controllpanelgg-monthly-addon\files-git-diff\" From 35e54be1557981e2ab63c2b03ed6132caa1cf400 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Sun, 19 Jun 2022 00:13:54 +0200 Subject: [PATCH 066/514] =?UTF-8?q?docs:=20=F0=9F=93=9D=20Updated=20Export?= =?UTF-8?q?=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Addon-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Addon-notes.md b/Addon-notes.md index 3c335a7f6..baf40c6aa 100644 --- a/Addon-notes.md +++ b/Addon-notes.md @@ -1,3 +1,3 @@ Export diff files: -cp -pv --parents $(git diff <commit> --name-only) "..\controllpanelgg-monthly-addon\files-git-diff\" +git diff -r --no-commit-id --name-only --diff-filter=ACMR <commit> | tar -czf file.tgz -T - From dfdba1e26ee6e1d85646255e2f1c4aa6d198a40f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Wed, 22 Jun 2022 12:01:47 +0200 Subject: [PATCH 067/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20No=20pro?= =?UTF-8?q?duct=20available=20at=20creation=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/admin/products/create.blade.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index 913fda1f0..fa8884d22 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -171,20 +171,16 @@ class="fas fa-info-circle"></i></label> <select id="billing_period" style="width:100%" class="custom-select" name="billing_period" required autocomplete="off" @error('billing_period') is-invalid @enderror> - <option value="hourly" @if ($product->billing_period == 'hourly') selected - @endif> + <option value="hourly" selected> {{__('Hourly')}} </option> - <option value="daily" @if ($product->billing_period == 'daily') selected - @endif> + <option value="daily"> {{__('Daily')}} </option> - <option value="weekly" @if ($product->billing_period == 'weekly') selected - @endif> + <option value="weekly"> {{__('Weekly')}} </option> - <option value="monthly" @if ($product->billing_period == 'monthly') selected - @endif> + <option value="monthly"> {{__('Monthly')}} </option> </select> From 994314dbe4673e25bae89c057130c2766edc675b Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 01:50:45 +0200 Subject: [PATCH 068/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20migrations?= =?UTF-8?q?=20undo=20last=20patch=20db=20update=20&=20add=20cancelation=20?= =?UTF-8?q?of=20servers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...34527_add_cancelation_to_servers_table.php | 32 +++++++++++++++++++ ...022_07_21_234818_undo_decimal_in_price.php | 32 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php create mode 100644 database/migrations/2022_07_21_234818_undo_decimal_in_price.php diff --git a/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php b/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php new file mode 100644 index 000000000..b9f758391 --- /dev/null +++ b/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class AddCancelationToServersTable extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('servers', function (Blueprint $table) { + $table->boolean('canceled')->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropColumn('canceled'); + }); + } +} diff --git a/database/migrations/2022_07_21_234818_undo_decimal_in_price.php b/database/migrations/2022_07_21_234818_undo_decimal_in_price.php new file mode 100644 index 000000000..cf4abb69b --- /dev/null +++ b/database/migrations/2022_07_21_234818_undo_decimal_in_price.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +class UndoDecimalInPrice extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('products', function (Blueprint $table) { + $table->decimal('price', 15, 4)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('products', function (Blueprint $table) { + $table->decimal('price',['11','2'])->change(); + }); + } +} From 642fef6864ca119577fb008d0368d8a41350eec3 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 02:58:08 +0200 Subject: [PATCH 069/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Cancel=20Bu?= =?UTF-8?q?tton=20&=20Next=20Billing=20Cycle=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 116 ++++++++++++++++++------ 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 517c3f1ea..9c9d81b06 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -40,7 +40,6 @@ class="fa fa-plus mr-2"></i> <div class="row d-flex flex-row justify-content-center justify-content-md-start"> @foreach ($servers as $server) - <div class="col-xl-3 col-lg-5 col-md-6 col-sm-6 col-xs-12 card pr-0 pl-0 ml-sm-2 mr-sm-3" style="max-width: 350px"> <div class="card-header"> @@ -107,7 +106,7 @@ class="fas fa-info-circle"></i> <span>{{ $server->egg }}</span> </div> </div> - <div class="row mb-4"> + <div class="row mb-2"> <div class="col-5 "> {{ __('Resource plan') }}: </div> @@ -115,11 +114,43 @@ class="fas fa-info-circle"></i> <span>{{ $server->product->name }} </span> <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/>" + data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/> {{ __('Billing Period') }}: {{$server->product->billing_period}}" class="fas fa-info-circle"></i> </div> + </div> + <div class="row mb-4 "> + <div class="col-5 "> + {{ __('Next Billing Cycle') }}: + </div> + <div class="col-7 d-flex text-wrap align-items-center"> + <span> + @switch($server->product->billing_period) + @case('monthly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonth()->toDayDateTimeString(); }} + @break + @case('weekly') + {{ \Carbon\Carbon::parse($server->last_billed)->addWeek()->toDayDateTimeString(); }} + @break + @case('daily') + {{ \Carbon\Carbon::parse($server->last_billed)->addDay()->toDayDateTimeString(); }} + @break + @case('hourly') + {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} + @break + @case('half-yearly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} + @break + @case('yearly') + {{ \Carbon\Carbon::parse($server->last_billed)->addYear()->toDayDateTimeString(); }} + @break + @default + {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} + @endswitch + </span> + </div> </div> + <div class="row mb-2"> <div class="col-4"> {{ __('Price') }}: @@ -147,17 +178,22 @@ class="fas fa-info-circle"></i> </div> </div> - <div class="card-footer d-flex align-items-center justify-content-between"> + <div class="card-footer text-center"> <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" target="__blank" - class="btn btn-info mx-3 w-100 align-items-center justify-content-center d-flex"> - <i class="fas fa-tools mr-2"></i> - <span>{{ __('Manage') }}</span> + class="btn btn-info text-center float-left ml-2" + data-toggle="tooltip" data-placement="bottom" title="Manage Server"> + <i class="fas fa-tools mx-4"></i> </a> - <button onclick="confirmSubmit('{{ $server->id }}', handleServerDelete);" target="__blank" - class="btn btn-danger mx-3 w-100 align-items-center justify-content-center d-flex"> - <i class="fas fa-trash mr-2"></i> - <span>{{ __('Delete') }}</span> + <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" + class="btn btn-warning text-center" + data-toggle="tooltip" data-placement="bottom" title="Cancel Server"> + <i class="fas fa-ban mx-4"></i> + </button> + <button onclick="handleServerDelete('{{ $server->id }}');" target="__blank" + class="btn btn-danger text-center float-right mr-2" + data-toggle="tooltip" data-placement="bottom" title="Delete Server"> + <i class="fas fa-trash mx-4"></i> </button> </div> </div> @@ -169,40 +205,66 @@ class="btn btn-danger mx-3 w-100 align-items-center justify-content-center d-fle <!-- END CONTENT --> <script> - const confirmSubmit = (serverId, handleServerDelete) => { - // Confirm delete submit with sweetalert + const handleServerCancel = (serverId) => { + // Handle server cancel with sweetalert Swal.fire({ - title: "{{ __('Are you sure?') }}", - text: "{{ __('This is an irreversible action, all files of this server will be removed.') }}", + title: "{{ __('Cancel Server?') }}", + text: "{{ __('This will cancel your current server to the next billing period. It will get suspended when the current period runs out.') }}", icon: 'warning', confirmButtonColor: '#d9534f', showCancelButton: true, - confirmButtonText: "{{ __('Yes, delete it!') }}", - cancelButtonText: "{{ __('No, cancel!') }}", + confirmButtonText: "{{ __('Yes, cancel it!') }}", + cancelButtonText: "{{ __('No, abort!') }}", reverseButtons: true }).then((result) => { if (result.value) { - handleServerDelete(serverId); + // Delete server + fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + window.location.reload(); + }); return } - Swal.fire("{{ __('Canceled ...') }}", `{{ __('Deletion has been canceled.') }}`, 'info'); - }); + }) } const handleServerDelete = (serverId) => { - // Delete server - fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { - method: 'DELETE', - headers: { - 'X-CSRF-TOKEN': '{{ csrf_token() }}' + Swal.fire({ + title: "{{ __('Delete Server?') }}", + text: "{{ __('This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.') }}", + icon: 'warning', + confirmButtonColor: '#d9534f', + showCancelButton: true, + confirmButtonText: "{{ __('Yes, delete it!') }}", + cancelButtonText: "{{ __('No, abort!') }}", + reverseButtons: true + }).then((result) => { + if (result.value) { + // Delete server + fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + window.location.reload(); + }); + return } - }).then(() => { - window.location.reload(); }); + } document.addEventListener('DOMContentLoaded', () => { $('[data-toggle="popover"]').popover(); }); + + $(function () { + $('[data-toggle="tooltip"]').tooltip() + }) </script> @endsection From f8c33d43befbde99866b9bdb4a290661ad91d4e6 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 03:51:05 +0200 Subject: [PATCH 070/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2022_07_21_234527_add_cancelation_to_servers_table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php b/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php index b9f758391..15fafb168 100644 --- a/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php +++ b/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php @@ -14,7 +14,7 @@ class AddCancelationToServersTable extends Migration public function up() { Schema::table('servers', function (Blueprint $table) { - $table->boolean('canceled')->default(false); + $table->dateTime('cancelled')->nullable(); }); } @@ -26,7 +26,7 @@ public function up() public function down() { Schema::table('servers', function (Blueprint $table) { - $table->dropColumn('canceled'); + $table->dropColumn('cancelled'); }); } } From 7fa9bf206243af2dab4c51d74d781ae0f73048c0 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 03:52:49 +0200 Subject: [PATCH 071/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Server=20Ca?= =?UTF-8?q?ncelation=20route=20method=20and=20charging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 8 ++++---- .../Controllers/Admin/ServerController.php | 20 ++++++++++++++++++- app/Http/Controllers/ServerController.php | 16 ++++++++++++++- app/Models/Server.php | 3 ++- resources/views/servers/index.blade.php | 4 ++-- routes/web.php | 2 ++ 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 769909c32..2a5d8a5ad 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -81,14 +81,14 @@ public function handle() continue; } - // check if user has enough credits to charge the server - if ($user->credits < $product->price) { + // check if the server is canceled or if user has enough credits to charge the server or + if ( $server->cancelled || $user->credits < $product->price) { try { - #suspend server + // suspend server $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); $server->suspend(); - #add user to notify list + // add user to notify list if (!in_array($user, $this->usersToNotify)) { array_push($this->usersToNotify, $user); } diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php index 06d2cc22b..b4e06ed1d 100644 --- a/app/Http/Controllers/Admin/ServerController.php +++ b/app/Http/Controllers/Admin/ServerController.php @@ -133,7 +133,25 @@ public function destroy(Server $server) } /** - * @param Server $server + * Cancel the Server billing cycle. + * + * @param Server $server + * @return RedirectResponse|Response + */ + public function cancel (Server $server) + { + try { + error_log($server->update([ + 'cancelled' => now(), + ])); + return redirect()->route('servers.index')->with('success', __('Server cancelled')); + } catch (Exception $e) { + return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to cancel the server"') . $e->getMessage() . '"'); + } + } + + /** + * @param Server $server * @return RedirectResponse */ public function toggleSuspended(Server $server) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index b066e77d6..bef8338a1 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -246,7 +246,21 @@ public function destroy(Server $server) return redirect()->route('servers.index')->with('success', __('Server removed')); } catch (Exception $e) { - return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to remove a resource "').$e->getMessage().'"'); + return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to delete the server"') . $e->getMessage() . '"'); + } + } + + /** Cancel Server */ + public function cancel (Server $server) + { + try { + error_log($server->update([ + 'cancelled' => now(), + ])); + + return redirect()->route('servers.index')->with('success', __('Server cancelled')); + } catch (Exception $e) { + return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to cancel the server"') . $e->getMessage() . '"'); } } diff --git a/app/Models/Server.php b/app/Models/Server.php index c3ea621a3..b2c19b8be 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -54,7 +54,8 @@ public function getActivitylogOptions(): LogOptions "identifier", "product_id", "pterodactyl_id", - "last_billed" + "last_billed", + "cancelled" ]; /** diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 9c9d81b06..e96b46b12 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -219,8 +219,8 @@ class="btn btn-danger text-center float-right mr-2" }).then((result) => { if (result.value) { // Delete server - fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { - method: 'DELETE', + fetch("{{ route('servers.cancel', '') }}" + '/' + serverId, { + method: 'PATCH', headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' } diff --git a/routes/web.php b/routes/web.php index c86a0c66c..d980e3a4e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -72,6 +72,7 @@ //normal routes Route::get('notifications/readAll', [NotificationController::class, 'readAll'])->name('notifications.readAll'); Route::resource('notifications', NotificationController::class); + Route::patch('/servers/cancel/{server}', [ServerController::class, 'cancel'])->name('servers.cancel'); Route::resource('servers', ServerController::class); if (config('SETTINGS::SYSTEM:ENABLE_UPGRADE')) { Route::post('servers/{server}/upgrade', [ServerController::class, 'upgrade'])->name('servers.upgrade'); @@ -140,6 +141,7 @@ //servers Route::get('servers/datatable', [AdminServerController::class, 'datatable'])->name('servers.datatable'); Route::post('servers/togglesuspend/{server}', [AdminServerController::class, 'toggleSuspended'])->name('servers.togglesuspend'); + Route::patch('/servers/cancel/{server}', [AdminServerController::class, 'cancel'])->name('servers.cancel'); Route::get('servers/sync', [AdminServerController::class, 'syncServers'])->name('servers.sync'); Route::resource('servers', AdminServerController::class); From 759ba599881463b530dcbc33b6f1bcb3d9335265 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:00:23 +0200 Subject: [PATCH 072/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20yearly=20an?= =?UTF-8?q?d=20half-yearly=20billing=20periods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 6 ++++++ app/Http/Controllers/Admin/ProductController.php | 4 ++-- resources/views/servers/index.blade.php | 4 ++++ themes/default/views/admin/products/create.blade.php | 6 ++++++ themes/default/views/admin/products/edit.blade.php | 8 ++++++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 2a5d8a5ad..45f18c2b1 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -61,6 +61,12 @@ public function handle() // check if server is due to be charged by comparing its last_billed date with the current date and the billing period $newBillingDate = null; switch($billing_period) { + case 'yearly': + $newBillingDate = Carbon::parse($server->last_billed)->addYear(); + break; + case 'half-yearly': + $newBillingDate = Carbon::parse($server->last_billed)->addMonths(6); + break; case 'monthly': $newBillingDate = Carbon::parse($server->last_billed)->addMonth(); break; diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 2893a262a..9e6c6dc2c 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -72,7 +72,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly", + "billing_period" => "required|in:hourly,daily,weekly,monthly,half-yearly,yearly", ]); $disabled = ! is_null($request->input('disabled')); @@ -139,7 +139,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly", + "billing_period" => "required|in:hourly,daily,weekly,monthly,half-yearly,yearly", ]); $disabled = ! is_null($request->input('disabled')); diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index e96b46b12..1d0edd657 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -162,6 +162,10 @@ class="fas fa-info-circle"></i> <div class="text-muted"> @if($server->product->billing_period == 'monthly') {{ __('per Month') }} + @elseif($server->product->billing_period == 'half-yearly') + {{ __('per 6 Months') }} + @elseif($server->product->billing_period == 'yearly') + {{ __('per Year') }} @elseif($server->product->billing_period == 'weekly') {{ __('per Week') }} @elseif($server->product->billing_period == 'daily') diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index fa8884d22..324c03c5f 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -183,6 +183,12 @@ class="fas fa-info-circle"></i></label> <option value="monthly"> {{__('Monthly')}} </option> + <option value="half-yearly"> + {{__('Half Yearly')}} + </option> + <option value="yearly"> + {{__('Yearly')}} + </option> </select> @error('billing_period') <div class="invalid-feedback"> diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index cedbc72c8..3401577b5 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -190,6 +190,14 @@ class="fas fa-info-circle"></i></label> @endif> {{__('Monthly')}} </option> + <option value="half-yearly" @if ($product->billing_period == 'half-yearly') selected + @endif> + {{__('Half Yearly')}} + </option> + <option value="yearly" @if ($product->billing_period == 'yearly') selected + @endif> + {{__('Yearly')}} + </option> </select> @error('billing_period') <div class="invalid-feedback"> From 34bd88a4f607e892b2fb5219eb75ceeabccaed9b Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:09:34 +0200 Subject: [PATCH 073/514] =?UTF-8?q?refactor:=20=F0=9F=9A=9A=20Rename=20Yea?= =?UTF-8?q?rly=20->=20Annually?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 4 +- .../Controllers/Admin/ProductController.php | 4 +- resources/views/servers/index.blade.php | 62 +++++++++++-------- .../views/admin/products/create.blade.php | 8 +-- .../views/admin/products/edit.blade.php | 8 +-- 5 files changed, 47 insertions(+), 39 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 45f18c2b1..dac01f0f8 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -61,10 +61,10 @@ public function handle() // check if server is due to be charged by comparing its last_billed date with the current date and the billing period $newBillingDate = null; switch($billing_period) { - case 'yearly': + case 'annually': $newBillingDate = Carbon::parse($server->last_billed)->addYear(); break; - case 'half-yearly': + case 'half-annually': $newBillingDate = Carbon::parse($server->last_billed)->addMonths(6); break; case 'monthly': diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 9e6c6dc2c..78ad6c997 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -72,7 +72,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly,half-yearly,yearly", + "billing_period" => "required|in:hourly,daily,weekly,monthly,half-annually,annually", ]); $disabled = ! is_null($request->input('disabled')); @@ -139,7 +139,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly,half-yearly,yearly", + "billing_period" => "required|in:hourly,daily,weekly,monthly,half-annually,annually", ]); $disabled = ! is_null($request->input('disabled')); diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 1d0edd657..fdcf2d104 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -72,9 +72,13 @@ class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') } <div class="row mb-3"> <div class="col my-auto">{{ __('Status') }}:</div> <div class="col-7 my-auto"> - <i - class="fas {{ $server->isSuspended() ? 'text-danger' : 'text-success' }} fa-circle mr-2"></i> - {{ $server->isSuspended() ? 'Suspended' : 'Active' }} + @if($server->suspennded) + <span class="badge badge-danger">{{ __('Suspended') }}</span> + @elseif($server->cancelled) + <span class="badge badge-warning">{{ __('Cancelled') }}</span> + @else + <span class="badge badge-success">{{ __('Active') }}</span> + @endif </div> </div> <div class="row mb-2"> @@ -125,28 +129,32 @@ class="fas fa-info-circle"></i> </div> <div class="col-7 d-flex text-wrap align-items-center"> <span> - @switch($server->product->billing_period) - @case('monthly') - {{ \Carbon\Carbon::parse($server->last_billed)->addMonth()->toDayDateTimeString(); }} - @break - @case('weekly') - {{ \Carbon\Carbon::parse($server->last_billed)->addWeek()->toDayDateTimeString(); }} - @break - @case('daily') - {{ \Carbon\Carbon::parse($server->last_billed)->addDay()->toDayDateTimeString(); }} - @break - @case('hourly') - {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} - @break - @case('half-yearly') - {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} - @break - @case('yearly') - {{ \Carbon\Carbon::parse($server->last_billed)->addYear()->toDayDateTimeString(); }} - @break - @default - {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} - @endswitch + @if ($server->cancelled) + - + @else + @switch($server->product->billing_period) + @case('monthly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonth()->toDayDateTimeString(); }} + @break + @case('weekly') + {{ \Carbon\Carbon::parse($server->last_billed)->addWeek()->toDayDateTimeString(); }} + @break + @case('daily') + {{ \Carbon\Carbon::parse($server->last_billed)->addDay()->toDayDateTimeString(); }} + @break + @case('hourly') + {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} + @break + @case('half-annually') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} + @break + @case('annually') + {{ \Carbon\Carbon::parse($server->last_billed)->addYear()->toDayDateTimeString(); }} + @break + @default + {{ __('Unknown') }} + @endswitch + @endif </span> </div> </div> @@ -162,9 +170,9 @@ class="fas fa-info-circle"></i> <div class="text-muted"> @if($server->product->billing_period == 'monthly') {{ __('per Month') }} - @elseif($server->product->billing_period == 'half-yearly') + @elseif($server->product->billing_period == 'half-annually') {{ __('per 6 Months') }} - @elseif($server->product->billing_period == 'yearly') + @elseif($server->product->billing_period == 'annually') {{ __('per Year') }} @elseif($server->product->billing_period == 'weekly') {{ __('per Week') }} diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index 324c03c5f..ed490c2e0 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -183,11 +183,11 @@ class="fas fa-info-circle"></i></label> <option value="monthly"> {{__('Monthly')}} </option> - <option value="half-yearly"> - {{__('Half Yearly')}} + <option value="half-annually"> + {{__('Half Annually')}} </option> - <option value="yearly"> - {{__('Yearly')}} + <option value="annually"> + {{__('Annually')}} </option> </select> @error('billing_period') diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index 3401577b5..9d3aec529 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -190,13 +190,13 @@ class="fas fa-info-circle"></i></label> @endif> {{__('Monthly')}} </option> - <option value="half-yearly" @if ($product->billing_period == 'half-yearly') selected + <option value="half-annually" @if ($product->billing_period == 'half-annually') selected @endif> - {{__('Half Yearly')}} + {{__('Half Annually')}} </option> - <option value="yearly" @if ($product->billing_period == 'yearly') selected + <option value="annually" @if ($product->billing_period == 'annually') selected @endif> - {{__('Yearly')}} + {{__('Annually')}} </option> </select> @error('billing_period') From 9ad2954f0e10b398d66e93b793cc1287664b3e08 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:15:40 +0200 Subject: [PATCH 074/514] =?UTF-8?q?fix:=20=F0=9F=93=9D=20Undo=20Naming?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index bef8338a1..41644158f 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -246,7 +246,7 @@ public function destroy(Server $server) return redirect()->route('servers.index')->with('success', __('Server removed')); } catch (Exception $e) { - return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to delete the server"') . $e->getMessage() . '"'); + return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to remove a resource"') . $e->getMessage() . '"'); } } From 2c4b3ea03ec272ab14bb9cd27dbe676f91dd833c Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:25:23 +0200 Subject: [PATCH 075/514] =?UTF-8?q?chore:=20=F0=9F=8C=90=20Localization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/de.json | 21 +++++++++++++++++++++ lang/sh.json | 23 ++++++++++++++++++++++- resources/views/servers/index.blade.php | 6 +++--- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/lang/de.json b/lang/de.json index 9107634ba..8c193fae4 100644 --- a/lang/de.json +++ b/lang/de.json @@ -459,6 +459,27 @@ "zh": "Chinesisch", "tr": "Türkisch", "ru": "Russisch", + "hourly": "Stündlich", + "monthly": "Monatlich", + "yearly": "Jährlich", + "daily": "Täglich", + "weekly": "Wöchentlich", + "half-annually": "Halbjährlich", + "annually": "Jährlich", + "Cancelled": "Gekündigt", + "An exception has occurred while trying to cancel the server": "Ein Fehler ist aufgetreten beim Versuch, den Server zu kündigen", + "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "Dies wird Ihren aktuellen Server zur nächsten Abrechnungsperiode kündigen. Er wird beim Ablauf der aktuellen Periode gesperrt.", + "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.": "Dies ist eine irreversibel Aktion, alle Dateien dieses Servers werden gelöscht. Keine Gelder werden zurückgezahlt. Wir empfehlen, den Server zu löschen, wenn er gesperrt ist.", + "Cancel Server?": "Server kündigen?", + "Delete Server?": "Server löschen?", + "Billing Period": "Abrechnungsperiode", + "Next Billing Cycle": "Nächste Abrechnungsperiode", + "Manage Server": "Server verwalten", + "Delete Server": "Server löschen", + "Cancel Server": "Server kündigen", + "Yes, cancel it!": "Ja, löschen!", + "No, abort!": "Abbrechen", + "Billing period": "Abrechnungsperiode", "sv": "Schwedisch", "sk": "Slowakisch", "Imprint": "Impressum", diff --git a/lang/sh.json b/lang/sh.json index 2755ad282..2badf679a 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -441,5 +441,26 @@ "pl": "Polish", "zh": "Chinese", "tr": "Turkish", - "ru": "Russian" + "ru": "Russian", + "hourly": "Hourly", + "monthly": "Monthly", + "yearly": "Yearly", + "daily": "Daily", + "weekly": "Weekly", + "half-annually": "Half-annually", + "annually": "Annually", + "Cancelled": "Cancelled", + "An exception has occurred while trying to cancel the server": "An exception has occurred while trying to cancel the server", + "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.", + "Cancel Server?": "Cancel Server?", + "Delete Server?": "Delete Server?", + "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.": "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.", + "Billing Period": "Billing Period", + "Next Billing Cycle": "Next Billing Cycle", + "Manage Server": "Manage Server", + "Delete Server": "Delete Server", + "Cancel Server": "Cancel Server", + "Yes, cancel it!": "Yes, cancel it!", + "No, abort!": "No, abort!", + "Billing period": "Billing period" } diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index fdcf2d104..d907a2780 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -194,17 +194,17 @@ class="fas fa-info-circle"></i> <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" target="__blank" class="btn btn-info text-center float-left ml-2" - data-toggle="tooltip" data-placement="bottom" title="Manage Server"> + data-toggle="tooltip" data-placement="bottom" title="{{ __('Manage Server') }}"> <i class="fas fa-tools mx-4"></i> </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" - data-toggle="tooltip" data-placement="bottom" title="Cancel Server"> + data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-4"></i> </button> <button onclick="handleServerDelete('{{ $server->id }}');" target="__blank" class="btn btn-danger text-center float-right mr-2" - data-toggle="tooltip" data-placement="bottom" title="Delete Server"> + data-toggle="tooltip" data-placement="bottom" title="{{ __('Delete Server') }}"> <i class="fas fa-trash mx-4"></i> </button> </div> From ecfa16045030b748a4fabd0578494be7e4fc0e4f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:31:00 +0200 Subject: [PATCH 076/514] =?UTF-8?q?fix:=20=F0=9F=92=84=20Added=20hyphens?= =?UTF-8?q?=20at=20next=20billing=20cycle=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index d907a2780..df7bd1789 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -124,7 +124,7 @@ class="fas fa-info-circle"></i> </div> <div class="row mb-4 "> - <div class="col-5 "> + <div class="col-5 word-break" style="hyphens: auto"> {{ __('Next Billing Cycle') }}: </div> <div class="col-7 d-flex text-wrap align-items-center"> From 8a4273a8fbefd94181d6f426c2607d90503d9ed0 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:33:54 +0200 Subject: [PATCH 077/514] =?UTF-8?q?style:=20=F0=9F=92=84=20Changed=20Next?= =?UTF-8?q?=20Billing=20Cycle=20behaviour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index df7bd1789..9b5a3ac9d 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -129,7 +129,7 @@ class="fas fa-info-circle"></i> </div> <div class="col-7 d-flex text-wrap align-items-center"> <span> - @if ($server->cancelled) + @if ($server->suspended) - @else @switch($server->product->billing_period) From 54e14d5f2b9b5b3b80d19011a2f06418622854d1 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:40:03 +0200 Subject: [PATCH 078/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Fiy=20suspended?= =?UTF-8?q?=20typo=20&=20localization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/de.json | 1 + lang/sh.json | 1 + resources/views/servers/index.blade.php | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lang/de.json b/lang/de.json index 8c193fae4..a9a5061b4 100644 --- a/lang/de.json +++ b/lang/de.json @@ -466,6 +466,7 @@ "weekly": "Wöchentlich", "half-annually": "Halbjährlich", "annually": "Jährlich", + "Suspended": "Gesperrt", "Cancelled": "Gekündigt", "An exception has occurred while trying to cancel the server": "Ein Fehler ist aufgetreten beim Versuch, den Server zu kündigen", "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "Dies wird Ihren aktuellen Server zur nächsten Abrechnungsperiode kündigen. Er wird beim Ablauf der aktuellen Periode gesperrt.", diff --git a/lang/sh.json b/lang/sh.json index 2badf679a..fc9457eb7 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -449,6 +449,7 @@ "weekly": "Weekly", "half-annually": "Half-annually", "annually": "Annually", + "Suspended": "Suspended", "Cancelled": "Cancelled", "An exception has occurred while trying to cancel the server": "An exception has occurred while trying to cancel the server", "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.", diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 9b5a3ac9d..0fdac5a2f 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -72,7 +72,7 @@ class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') } <div class="row mb-3"> <div class="col my-auto">{{ __('Status') }}:</div> <div class="col-7 my-auto"> - @if($server->suspennded) + @if($server->suspended) <span class="badge badge-danger">{{ __('Suspended') }}</span> @elseif($server->cancelled) <span class="badge badge-warning">{{ __('Cancelled') }}</span> @@ -199,6 +199,7 @@ class="btn btn-info text-center float-left ml-2" </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" + disabled="{{ $server->suspended }}" data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-4"></i> </button> From e310dba243f10d0ebe704d66dd581a8aa7be227b Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 04:54:33 +0200 Subject: [PATCH 079/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Credt=20usage=20a?= =?UTF-8?q?t=20dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/HomeController.php | 2 +- app/Models/Product.php | 16 +++++++++++++++- app/Models/User.php | 10 +++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 5a5117589..db00d18d1 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -97,7 +97,7 @@ public function index(Request $request) /** Build our Time-Left-Box */ if ($credits > 0.01 and $usage > 0) { - $daysLeft = number_format(($credits * 30) / $usage, 2, '.', ''); + $daysLeft = number_format($credits / ($usage / 30), 2, '.', ''); $hoursLeft = number_format($credits / ($usage / 30 / 24), 2, '.', ''); $bg = $this->getTimeLeftBoxBackground($daysLeft); diff --git a/app/Models/Product.php b/app/Models/Product.php index a31ebf50a..c55d3be85 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -43,7 +43,21 @@ public static function boot() public function getHourlyPrice() { - return ($this->price / 30) / 24; + // calculate the hourly price with the billing period + switch($this->billing_period) { + case 'daily': + return $this->price / 24; + case 'weekly': + return $this->price / 24 / 7; + case 'monthly': + return $this->price / 24 / 30; + case 'half-annually': + return $this->price / 24 / 30 / 6; + case 'annually': + return $this->price / 24 / 365; + default: + return $this->price; + } } public function getDailyPrice() diff --git a/app/Models/User.php b/app/Models/User.php index 85e2b2405..91e15cab3 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -251,12 +251,20 @@ public function creditUsage() { $usage = 0; foreach ($this->getServersWithProduct() as $server) { - $usage += $server->product->price; + $usage += $server->product->getHourlyPrice() * 24 * 30; } return number_format($usage, 2, '.', ''); } + private function getServersWithProduct() { + return $this->servers() + ->whereNull('suspended') + ->whereNull('cancelled') + ->with('product') + ->get(); + } + /** * @return array|string|string[] */ From e4165181fed498f94992f86902163da3db8d337d Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 05:06:38 +0200 Subject: [PATCH 080/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Disable=20Cancel?= =?UTF-8?q?=20Button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 0fdac5a2f..862174f3d 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -199,7 +199,7 @@ class="btn btn-info text-center float-left ml-2" </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" - disabled="{{ $server->suspended }}" + {{ $server->suspended? "disabled" : "" }} data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-4"></i> </button> From 3f4cae352a10855152c6da332aa2e37076573b3f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 05:12:06 +0200 Subject: [PATCH 081/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Disable=20cancel?= =?UTF-8?q?=20button=20when=20cancelled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 862174f3d..8ccffc874 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -199,7 +199,7 @@ class="btn btn-info text-center float-left ml-2" </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" - {{ $server->suspended? "disabled" : "" }} + {{ $server->suspended || $server->cancelled ? "disabled" : "" }} data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-4"></i> </button> From 8565faa40b0dffd10e04da234afe04d520773107 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 05:26:48 +0200 Subject: [PATCH 082/514] =?UTF-8?q?chore:=20=F0=9F=8C=90=20Added=20Bold=20?= =?UTF-8?q?text=20for=20No=20refund?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/de.json | 2 +- lang/sh.json | 2 +- resources/views/servers/index.blade.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lang/de.json b/lang/de.json index a9a5061b4..45f1cc826 100644 --- a/lang/de.json +++ b/lang/de.json @@ -470,7 +470,7 @@ "Cancelled": "Gekündigt", "An exception has occurred while trying to cancel the server": "Ein Fehler ist aufgetreten beim Versuch, den Server zu kündigen", "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "Dies wird Ihren aktuellen Server zur nächsten Abrechnungsperiode kündigen. Er wird beim Ablauf der aktuellen Periode gesperrt.", - "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.": "Dies ist eine irreversibel Aktion, alle Dateien dieses Servers werden gelöscht. Keine Gelder werden zurückgezahlt. Wir empfehlen, den Server zu löschen, wenn er gesperrt ist.", + "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.": "Dies ist eine irreversiblen Aktion, alle Dateien dieses Servers werden gelöscht. <strong>Keine Rückerstattung!</strong>. Wir empfehlen, den Server zu löschen, wenn er gesperrt ist.", "Cancel Server?": "Server kündigen?", "Delete Server?": "Server löschen?", "Billing Period": "Abrechnungsperiode", diff --git a/lang/sh.json b/lang/sh.json index fc9457eb7..14c206ff5 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -455,7 +455,7 @@ "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.", "Cancel Server?": "Cancel Server?", "Delete Server?": "Delete Server?", - "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.": "This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.", + "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.": "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.", "Billing Period": "Billing Period", "Next Billing Cycle": "Next Billing Cycle", "Manage Server": "Manage Server", diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 8ccffc874..53490a292 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -248,7 +248,7 @@ class="btn btn-danger text-center float-right mr-2" const handleServerDelete = (serverId) => { Swal.fire({ title: "{{ __('Delete Server?') }}", - text: "{{ __('This is an irreversible action, all files of this server will be removed. No funds will get refunded. We recommend deleting the server when server is suspended.') }}", + html: "{{!! __('This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.') !!}}", icon: 'warning', confirmButtonColor: '#d9534f', showCancelButton: true, From e254b2acfed7da2911acbfb0a04e8e800b73a024 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Fri, 22 Jul 2022 05:31:05 +0200 Subject: [PATCH 083/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Quartely=20?= =?UTF-8?q?billing=20period?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 3 +++ app/Http/Controllers/Admin/ProductController.php | 4 ++-- app/Models/Product.php | 2 ++ lang/de.json | 1 + lang/sh.json | 1 + resources/views/servers/index.blade.php | 5 +++++ themes/default/views/admin/products/create.blade.php | 3 +++ themes/default/views/admin/products/edit.blade.php | 4 ++++ 8 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index dac01f0f8..72b5bab4d 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -67,6 +67,9 @@ public function handle() case 'half-annually': $newBillingDate = Carbon::parse($server->last_billed)->addMonths(6); break; + case 'quarterly': + $newBillingDate = Carbon::parse($server->last_billed)->addMonths(3); + break; case 'monthly': $newBillingDate = Carbon::parse($server->last_billed)->addMonth(); break; diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 78ad6c997..ca997ee82 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -72,7 +72,7 @@ public function store(Request $request) "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly,half-annually,annually", + "billing_period" => "required|in:hourly,daily,weekly,monthly,quarterly,half-annually,annually", ]); $disabled = ! is_null($request->input('disabled')); @@ -139,7 +139,7 @@ public function update(Request $request, Product $product): RedirectResponse "nodes.*" => "required|exists:nodes,id", "eggs.*" => "required|exists:eggs,id", "disabled" => "nullable", - "billing_period" => "required|in:hourly,daily,weekly,monthly,half-annually,annually", + "billing_period" => "required|in:hourly,daily,weekly,monthly,quarterly,half-annually,annually", ]); $disabled = ! is_null($request->input('disabled')); diff --git a/app/Models/Product.php b/app/Models/Product.php index c55d3be85..fbd16803a 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -51,6 +51,8 @@ public function getHourlyPrice() return $this->price / 24 / 7; case 'monthly': return $this->price / 24 / 30; + case 'quarterly': + return $this->price / 24 / 30 / 3; case 'half-annually': return $this->price / 24 / 30 / 6; case 'annually': diff --git a/lang/de.json b/lang/de.json index 45f1cc826..b01d61b70 100644 --- a/lang/de.json +++ b/lang/de.json @@ -464,6 +464,7 @@ "yearly": "Jährlich", "daily": "Täglich", "weekly": "Wöchentlich", + "quarterly": "Vierteljährlich", "half-annually": "Halbjährlich", "annually": "Jährlich", "Suspended": "Gesperrt", diff --git a/lang/sh.json b/lang/sh.json index 14c206ff5..cd3b0e50f 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -447,6 +447,7 @@ "yearly": "Yearly", "daily": "Daily", "weekly": "Weekly", + "quarterly": "Quarterly", "half-annually": "Half-annually", "annually": "Annually", "Suspended": "Suspended", diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 53490a292..a9c57994e 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -145,6 +145,9 @@ class="fas fa-info-circle"></i> @case('hourly') {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} @break + @case('quarterly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(3)->toDayDateTimeString(); }} + @break @case('half-annually') {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} @break @@ -172,6 +175,8 @@ class="fas fa-info-circle"></i> {{ __('per Month') }} @elseif($server->product->billing_period == 'half-annually') {{ __('per 6 Months') }} + @elseif($server->product->billing_period == 'quarterly') + {{ __('per 3 Months') }} @elseif($server->product->billing_period == 'annually') {{ __('per Year') }} @elseif($server->product->billing_period == 'weekly') diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index ed490c2e0..45216c256 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -183,6 +183,9 @@ class="fas fa-info-circle"></i></label> <option value="monthly"> {{__('Monthly')}} </option> + <option value="quarterly"> + {{__('Quarterly')}} + </option> <option value="half-annually"> {{__('Half Annually')}} </option> diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index 9d3aec529..851142af3 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -190,6 +190,10 @@ class="fas fa-info-circle"></i></label> @endif> {{__('Monthly')}} </option> + <option value="quarterly" @if ($product->billing_period == 'quarterly') selected + @endif> + {{__('Quarterly')}} + </option> <option value="half-annually" @if ($product->billing_period == 'half-annually') selected @endif> {{__('Half Annually')}} From 87ec49008d377812c70ef1036298a99f985fea42 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Wed, 27 Jul 2022 23:26:03 +0200 Subject: [PATCH 084/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20Fixed=20?= =?UTF-8?q?credits=20check=20at=20server=20creation=20&=20formatted=20pric?= =?UTF-8?q?es=20nicely?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 2 +- app/Http/Controllers/ServerController.php | 3 ++- resources/views/servers/index.blade.php | 2 +- themes/default/views/servers/create.blade.php | 25 +++++++++++++------ 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 72b5bab4d..014af9636 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -91,7 +91,7 @@ public function handle() } // check if the server is canceled or if user has enough credits to charge the server or - if ( $server->cancelled || $user->credits < $product->price) { + if ( $server->cancelled || $user->credits <= $product->price) { try { // suspend server $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 41644158f..f793ac3bf 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -131,7 +131,8 @@ private function validateConfigurationRules() Auth::user()->credits < ($product->minimum_credits == -1 ? config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50) - : $product->minimum_credits) + : $product->minimum_credits) || + Auth::user()->credits <= $product->price ) { return redirect()->route('servers.index')->with('error', 'You do not have the required amount of '.CREDITS_DISPLAY_NAME.' to use this product!'); } diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index a9c57994e..0e5d69505 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -188,7 +188,7 @@ class="fas fa-info-circle"></i> @endif </div> <span> - {{ $server->product->price }} + {{ number_format($server->product->price) }} </span> </div> </div> diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index b184d4626..2a0a3d841 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -239,14 +239,14 @@ class="custom-select"> <input type="hidden" name="product" x-model="selectedProduct"> </div> <div> - <button type="submit" x-model="selectedProduct" name="product" - :disabled="product.minimum_credits > user.credits || product.doesNotFit == true || - submitClicked" - :class="product.minimum_credits > user.credits || product.doesNotFit == true || - submitClicked ? 'disabled' : ''" - class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" - x-text=" product.doesNotFit == true ? '{{ __('Server cant fit on this Node') }}' : (product.minimum_credits > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : '{{ __('Create server') }}')"> - </button> + <button type="submit" x-model="selectedProduct" name="product" + :disabled="product.minimum_credits > user.credits || product.price > user.credits || product.doesNotFit == true || + submitClicked" + :class="product.minimum_credits > user.credits || product.price > user.credits || product.doesNotFit == true || + submitClicked ? 'disabled' : ''" + class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" + x-text="product.doesNotFit == true ? '{{ __('Server cant fit on this Node') }}' : (product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : '{{ __('Create server') }}')"> + </button> </div> </div> </div> @@ -376,6 +376,7 @@ function serverApp() { .catch(console.error) this.fetchedProducts = true; + // TODO: Sortable by user chosen property (cpu, ram, disk...) this.products = response.data.sort((p1, p2) => parseInt(p1.price, 10) > parseInt(p2.price, 10) && 1 || -1) @@ -385,11 +386,19 @@ function serverApp() { product.cpu = product.cpu / 100; }) + //format price to have no decimals if it is a whole number + this.products.forEach(product => { + if (product.price % 1 === 0) { + product.price = Math.round(product.price); + } + }) + this.loading = false; this.updateSelectedObjects() }, + /** * @description map selected id's to selected objects * @note being used in the server info box From 35734579ae23eb0399684e3a64eab5af21982a14 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 28 Jul 2022 23:20:39 +0200 Subject: [PATCH 085/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Server=20creation?= =?UTF-8?q?=20fail=20when=20server.price=20=3D=20user.credits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index f793ac3bf..982e01f50 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -132,7 +132,7 @@ private function validateConfigurationRules() ($product->minimum_credits == -1 ? config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50) : $product->minimum_credits) || - Auth::user()->credits <= $product->price + Auth::user()->credits < $product->price ) { return redirect()->route('servers.index')->with('error', 'You do not have the required amount of '.CREDITS_DISPLAY_NAME.' to use this product!'); } From cf62cdef1ff6f9b34d20222c08ed27d3d3d41ba7 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Thu, 28 Jul 2022 23:33:04 +0200 Subject: [PATCH 086/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20See=20last=20comm?= =?UTF-8?q?it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 982e01f50..7a6e93d37 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -128,10 +128,7 @@ private function validateConfigurationRules() // Min. Credits if ( - Auth::user()->credits < - ($product->minimum_credits == -1 - ? config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50) - : $product->minimum_credits) || + Auth::user()->credits < $product->minimum_credits || Auth::user()->credits < $product->price ) { return redirect()->route('servers.index')->with('error', 'You do not have the required amount of '.CREDITS_DISPLAY_NAME.' to use this product!'); From d56bc09ac5608b4e1eab9809c4b359bae9382043 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Mon, 15 Aug 2022 14:52:49 +0200 Subject: [PATCH 087/514] =?UTF-8?q?fix:=20=F0=9F=92=84=20Styling=20of=20se?= =?UTF-8?q?rver=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resources/views/servers/index.blade.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 0e5d69505..223bbab2c 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -200,18 +200,25 @@ class="fas fa-info-circle"></i> target="__blank" class="btn btn-info text-center float-left ml-2" data-toggle="tooltip" data-placement="bottom" title="{{ __('Manage Server') }}"> - <i class="fas fa-tools mx-4"></i> + <i class="fas fa-tools mx-2"></i> </a> + @if(config("SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN")) + <a href="{{ route('servers.show', ['server' => $server->id])}}" + class="btn btn-info text-center mr-3" + data-toggle="tooltip" data-placement="bottom" title="{{ __('Server Settings') }}"> + <i class="fas fa-cog mx-2"></i> + </a> + @endif <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" {{ $server->suspended || $server->cancelled ? "disabled" : "" }} data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> - <i class="fas fa-ban mx-4"></i> + <i class="fas fa-ban mx-2"></i> </button> <button onclick="handleServerDelete('{{ $server->id }}');" target="__blank" class="btn btn-danger text-center float-right mr-2" data-toggle="tooltip" data-placement="bottom" title="{{ __('Delete Server') }}"> - <i class="fas fa-trash mx-4"></i> + <i class="fas fa-trash mx-2"></i> </button> </div> </div> From 973e77569af1aa7443ca3b961d6bb43aedaafb5c Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.cloud> Date: Mon, 15 Aug 2022 14:56:34 +0200 Subject: [PATCH 088/514] chore: Docs --- Addon-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Addon-notes.md b/Addon-notes.md index baf40c6aa..1d3887428 100644 --- a/Addon-notes.md +++ b/Addon-notes.md @@ -1,3 +1,3 @@ Export diff files: -git diff -r --no-commit-id --name-only --diff-filter=ACMR <commit> | tar -czf file.tgz -T - +git diff -r --no-commit-id --name-only --diff-filter=ACMR <commit> | tar -czf ../controllpanelgg-monthly-addon/file.tgz -T - From 56aad2a7cb30b0953e1b8110cea8b25d07604bbf Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Tue, 22 Nov 2022 12:23:33 +0100 Subject: [PATCH 089/514] fix: Number formatting on server overview (price) --- Addon-notes.md | 3 ++- resources/views/servers/index.blade.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Addon-notes.md b/Addon-notes.md index 1d3887428..bed6f5a2e 100644 --- a/Addon-notes.md +++ b/Addon-notes.md @@ -1,3 +1,4 @@ Export diff files: +Commit Hash of lates Main commit -git diff -r --no-commit-id --name-only --diff-filter=ACMR <commit> | tar -czf ../controllpanelgg-monthly-addon/file.tgz -T - +git diff -r --no-commit-id --name-only --diff-filter=ACMR \<commit> | tar -czf ../controllpanelgg-monthly-addon/file.tgz -T - diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php index 223bbab2c..7910a30af 100644 --- a/resources/views/servers/index.blade.php +++ b/resources/views/servers/index.blade.php @@ -188,7 +188,7 @@ class="fas fa-info-circle"></i> @endif </div> <span> - {{ number_format($server->product->price) }} + {{ $server->product->price == round($server->product->price) ? round($server->product->price) : $server->product->price }} </span> </div> </div> From 94cbea528c1c68bfcbcaf2b1f41125ebbc50f919 Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Tue, 22 Nov 2022 13:42:46 +0100 Subject: [PATCH 090/514] Fixed Upgrade/Downgrade Credit withdrawal --- app/Http/Controllers/ServerController.php | 79 ++++++++++++------- .../default/views/servers/settings.blade.php | 10 +-- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 7a6e93d37..61ac4218a 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -9,6 +9,8 @@ use App\Models\Node; use App\Models\Product; use App\Models\Server; +use App\Models\User; +use App\Models\Settings; use App\Notifications\ServerCreationError; use Carbon\Carbon; use Exception; @@ -231,8 +233,6 @@ private function noAllocationsError(Server $server) */ private function serverCreationFailed(Response $response, Server $server) { - $server->delete(); - return redirect()->route('servers.index')->with('error', json_encode($response->json())); } @@ -265,9 +265,9 @@ public function cancel (Server $server) /** Show Server Settings */ public function show(Server $server) { - if ($server->user_id != Auth::user()->id) { - return back()->with('error', __('´This is not your Server!')); - } + + + if($server->user_id != Auth::user()->id){ return back()->with('error', __('This is not your Server!'));} $serverAttributes = Pterodactyl::getServerAttributes($server->pterodactyl_id); $serverRelationships = $serverAttributes['relationships']; $serverLocationAttributes = $serverRelationships['location']['attributes']; @@ -308,10 +308,9 @@ public function show(Server $server) public function upgrade(Server $server, Request $request) { - if ($server->user_id != Auth::user()->id) { - return redirect()->route('servers.index'); - } - if (! isset($request->product_upgrade)) { + if($server->user_id != Auth::user()->id || $server->suspended) return redirect()->route('servers.index'); + if(!isset($request->product_upgrade)) + { return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('this product is the only one')); } $user = Auth::user(); @@ -333,27 +332,53 @@ public function upgrade(Server $server, Request $request) return redirect()->route('servers.index')->with('error', __("The node '".$nodeName."' doesn't have the required memory or disk left to upgrade the server.")); } - $priceupgrade = $newProduct->getHourlyPrice(); - - if ($priceupgrade < $oldProduct->getHourlyPrice()) { - $priceupgrade = 0; - } - if ($user->credits >= $priceupgrade && $user->credits >= $newProduct->minimum_credits) { - $server->product_id = $request->product_upgrade; - $server->update(); + // calculate the amount of credits that the user overpayed for the old product when canceling the server right now + // billing periods are hourly, daily, weekly, monthly, quarterly, half-annually, annually + $billingPeriod = $oldProduct->billing_period; + // seconds + $billingPeriods = [ + 'hourly' => 3600, + 'daily' => 86400, + 'weekly' => 604800, + 'monthly' => 2592000, + 'quarterly' => 7776000, + 'half-annually' => 15552000, + 'annually' => 31104000 + ]; + // Get the amount of hours the user has been using the server + $billingPeriodMultiplier = $billingPeriods[$billingPeriod]; + $timeDifference = now()->diffInSeconds($server->last_billed); + + // Calculate the price for the time the user has been using the server + $overpayedCredits = $oldProduct->price - $oldProduct->price * ($timeDifference / $billingPeriodMultiplier); + + + if ($user->credits >= $newProduct->price && $user->credits >= $newProduct->minimum_credits) + { $server->allocation = $serverAttributes['allocation']; + // Update the server on the panel $response = Pterodactyl::updateServer($server, $newProduct); - if ($response->failed()) { - return $this->serverCreationFailed($response, $server); - } - //update user balance - $user->decrement('credits', $priceupgrade); - //restart the server - $response = Pterodactyl::powerAction($server, 'restart'); - if ($response->failed()) { - return redirect()->route('servers.index')->with('error', $response->json()['errors'][0]['detail']); - } + if ($response->failed()) return $this->serverCreationFailed($response, $server); + + // Remove the allocation property from the server object as it is not a column in the database + unset($server->allocation); + // Update the server on controlpanel + $server->update([ + 'product_id' => $newProduct->id, + 'updated_at' => now(), + 'last_billed' => now(), + 'cancelled' => null, + ]); + // Refund the user the overpayed credits + if ($overpayedCredits > 0) $user->increment('credits', $overpayedCredits); + + // Withdraw the credits for the new product + $user->decrement('credits', $newProduct->price); + + //restart the server + $response = Pterodactyl::powerAction($server, "restart"); + if ($response->failed()) return redirect()->route('servers.index')->with('error', 'Server upgraded successfully! Could not restart the server: '.$response->json()['errors'][0]['detail']); return redirect()->route('servers.show', ['server' => $server->id])->with('success', __('Server Successfully Upgraded')); } else { return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('Not Enough Balance for Upgrade')); diff --git a/themes/default/views/servers/settings.blade.php b/themes/default/views/servers/settings.blade.php index 9fead6814..c03eeade7 100644 --- a/themes/default/views/servers/settings.blade.php +++ b/themes/default/views/servers/settings.blade.php @@ -243,10 +243,7 @@ class="btn btn-info btn-md"> </button> </div> <div class="modal-body card-body"> - <strong>{{__("FOR DOWNGRADE PLEASE CHOOSE A PLAN BELOW YOUR PLAN")}}</strong> - <br> - <br> - <strong>{{__("YOUR PRODUCT")}} : </strong> {{ $server->product->name }} + <strong>{{__("Current Product")}}: </strong> {{ $server->product->name }} <br> <br> @@ -256,12 +253,13 @@ class="btn btn-info btn-md"> <option value="">{{__("Select the product")}}</option> @foreach($products as $product) @if(in_array($server->egg, $product->eggs) && $product->id != $server->product->id && $product->disabled == false) - <option value="{{ $product->id }}" @if($product->doesNotFit)disabled @endif>{{ $product->name }} [ {{ CREDITS_DISPLAY_NAME }} {{ $product->price }} @if($product->doesNotFit)] {{__('Server can´t fit on this node')}} @else @if($product->minimum_credits!=-1) / + <option value="{{ $product->id }}" @if($product->doesNotFit)disabled @endif>{{ $product->name }} [ {{ CREDITS_DISPLAY_NAME }} {{ $product->price }} @if($product->doesNotFit)] {{__('Server can\'t fit on this node')}} @else @if($product->minimum_credits!=-1) / {{__("Required")}}: {{$product->minimum_credits}} {{ CREDITS_DISPLAY_NAME }}@endif ] @endif</option> @endif @endforeach </select> - <br> {{__("Once the Upgrade button is pressed, we will automatically deduct the amount for the first hour according to the new product from your credits")}}. <br> + + <br> <strong>{{__("Caution") }}:</strong> {{__("Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed")}}. <br> <br> {{__("Server will be automatically restarted once upgraded")}} </div> <div class="modal-footer card-body"> From ae787dfc4c68ffd0f1b8606dcfb479622d9c876a Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Tue, 22 Nov 2022 13:43:06 +0100 Subject: [PATCH 091/514] =?UTF-8?q?chore:=20=F0=9F=8C=90=20localization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/bg.json | 18 +++-- lang/bs.json | 16 ++-- lang/cs.json | 28 ++++--- lang/de.json | 15 ++-- lang/en.json | 222 ++++++++++++++------------------------------------- lang/es.json | 26 +++--- lang/fr.json | 16 ++-- lang/he.json | 46 ++++++----- lang/hi.json | 16 ++-- lang/hu.json | 16 ++-- lang/it.json | 16 ++-- lang/nl.json | 16 ++-- lang/pl.json | 16 ++-- lang/pt.json | 16 ++-- lang/ro.json | 16 ++-- lang/ru.json | 18 +++-- lang/sh.json | 45 +++-------- lang/sk.json | 16 ++-- lang/sr.json | 16 ++-- lang/sv.json | 16 ++-- lang/tr.json | 16 ++-- lang/zh.json | 16 ++-- 22 files changed, 279 insertions(+), 363 deletions(-) diff --git a/lang/bg.json b/lang/bg.json index dbd6881a4..bc1ab41ac 100644 --- a/lang/bg.json +++ b/lang/bg.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Грешка при създаване на сървър", "Your servers have been suspended!": "Сървърите ви са спрени!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "За да активирате автоматично вашия сървър\/и, трябва да закупите повече кредити.", + "To automatically re-enable your server/s, you need to purchase more credits.": "За да активирате автоматично вашия сървър/и, трябва да закупите повече кредити.", "Purchase credits": "Купете кредити", "If you have any questions please let us know.": "При допълнителни въпроси, моля свържете се с нас.", "Regards": "Поздрави", @@ -93,7 +93,7 @@ "Getting started!": "Приготвяме се да започнем!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Език по подразбиране", "The fallback Language, if something goes wrong": "Резервният език, ако нещо се обърка", "Datable language": "Език с данни", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Езиков код на таблиците с данни. <br><strong>Пример:<\/strong> en-gb, fr_fr, de_de<br>Повече информация: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Езиков код на таблиците с данни. <br><strong>Пример:</strong> en-gb, fr_fr, de_de<br>Повече информация: ", "Auto-translate": "Автоматичен превод", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Ако това е отметнато, таблото за управление ще се преведе на езика на клиентите, ако е наличен", "Client Language-Switch": "Превключване на клиентски език", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Таксува кредити за първия час при създаване на сървър.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Въведете URL адреса на вашата инсталация на PHPMyAdmin. <strong>Без крайна наклонена черта!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Въведете URL адреса на вашата инсталация на PHPMyAdmin. <strong>Без крайна наклонена черта!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Въведете URL адреса на вашата инсталация на Pterodactyl.<strong>Без крайна наклонена черта!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Въведете URL адреса на вашата инсталация на Pterodactyl.<strong>Без крайна наклонена черта!</strong>", "Pterodactyl API Key": "Pterodactyl API Kлюч", "Enter the API Key to your Pterodactyl installation.": "Въведете API ключа към вашата инсталация на Pterodactyl.", "Force Discord verification": "Принудително потвърждаване на Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Ваучер може да се използва само веднъж на потребител. Uses определя броя на различните потребители, които могат да използват този ваучер.", "Max": "Макс", "Expires at": "Изтича на", - "Used \/ Uses": "Използван \/ Използвания", + "Used / Uses": "Използван / Използвания", "Expires": "Изтича", "Sign in to start your session": "Влезте, за да започнете сесията си", "Password": "Парола", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Няма свързани Node-ове!", "No nests available!": "Няма налични Nest-ове!", "No eggs have been linked!": "Няма свързани Egg-ове!", - "Software \/ Games": "Софтуер \/ Игри", + "Software / Games": "Софтуер / Игри", "Please select software ...": "Моля, изберете софтуер...", "---": "---", "Specification ": "Спецификация ", @@ -460,5 +460,7 @@ "tr": "Турски", "ru": "Руски", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Актуализирането / надграждането на сървъра ви ще нулира вашата билнгова цикъл до сега. Вашите превишени кредити ще бъдат възстановени. Цената за новия билнгов цикъл ще бъде извлечена", + "Caution": "Внимание" } diff --git a/lang/bs.json b/lang/bs.json index 8a84b67a3..cb014f873 100644 --- a/lang/bs.json +++ b/lang/bs.json @@ -80,7 +80,7 @@ "User ID": "User ID", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -173,7 +173,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -214,9 +214,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -284,7 +284,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -354,7 +354,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -441,5 +441,7 @@ "pl": "Polish", "zh": "Chinese", "tr": "Turkish", - "ru": "Russian" + "ru": "Russian", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution" } diff --git a/lang/cs.json b/lang/cs.json index 7f7235420..04dc0090e 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Někdo se zaregistroval pomocí vašeho kódu!", "Server Creation Error": "Chyba při vytváření serveru", "Your servers have been suspended!": "Vaše servery byly pozastaveny!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Pro opětovné spuštění vašich serverů dobijte prosím kredity.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Pro opětovné spuštění vašich serverů dobijte prosím kredity.", "Purchase credits": "Zakoupit kredity", "If you have any questions please let us know.": "Máte-li jakékoli dotazy, dejte nám vědět.", "Regards": "S pozdravem", @@ -93,7 +93,7 @@ "Getting started!": "Začínáme!", "Welcome to our dashboard": "Vítejte v našem ovládacím panelu", "Verification": "Ověření", - "You can verify your e-mail address and link\/verify your Discord account.": "Můžete ověřit svojí e-mail adresu a přiojit váš Discord účet.", + "You can verify your e-mail address and link/verify your Discord account.": "Můžete ověřit svojí e-mail adresu a přiojit váš Discord účet.", "Information": "Informace", "This dashboard can be used to create and delete servers": "Tento panel může použít pro vytvoření a mazání serverů", "These servers can be used and managed on our pterodactyl panel": "Tyto servery můžete používat a spravovat v našem pterodactyl panelu", @@ -114,7 +114,7 @@ "Token": "Token", "Last used": "Naposledy použito", "Are you sure you wish to delete?": "Opravdu si přejete odstranit?", - "Nests": "Software\/hra", + "Nests": "Software/hra", "Sync": "Synchronizovat", "Active": "Aktivní", "ID": "ID", @@ -174,10 +174,10 @@ "please create a file called \"install.lock\" in your dashboard Root directory. Otherwise no settings will be loaded!": "prosím vytvoř soubor který bude pojmenován install.lock v Kontrolním panelu (hlavní složka)\nPokud tak neuděláš, žádné změny nebudou načteny!", "or click here": "nebo klikněte sem", "Company Name": "Název společnosti", - "Company Address": "Adresa Firmy", + "Company Adress": "Adresa Firmy", "Company Phonenumber": "Telefon společnosti", "VAT ID": "DIČ", - "Company E-Mail Address": "E-mailová adresa společnosti", + "Company E-Mail Adress": "E-mailová adresa společnosti", "Company Website": "Web společnosti", "Invoice Prefix": "Prefix pro faktury", "Enable Invoices": "Povolit faktury", @@ -187,7 +187,7 @@ "Default language": "Výchozí jazyk", "The fallback Language, if something goes wrong": "Záložní jazyk, kdyby se něco pokazilo", "Datable language": "Jazyk tabulek", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Kód jazyka datových tabulek. <br><strong>Příklad:<\/strong> en-gb, fr_fr, de_de<br>Více informací: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Kód jazyka datových tabulek. <br><strong>Příklad:</strong> en-gb, fr_fr, de_de<br>Více informací: ", "Auto-translate": "Automatický překlad", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Pokud je tohle zaškrtlé, Panel bude přeložen do Jazyka klienta (pokud to je možné)", "Client Language-Switch": "Povolit uživatelům změnu jazyka", @@ -199,7 +199,7 @@ "Mail Username": "Uživatelské jméno pro e-mail", "Mail Password": "E-mailové heslo", "Mail Encryption": "Šifrování e-mailu", - "Mail From Address": "E-mail odesílatele", + "Mail From Adress": "E-mail odesílatele", "Mail From Name": "Název odešílatele", "Discord Client-ID": "Discord Client-ID", "Discord Client-Secret": "Discord Client-Secret", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Po vytvoření služby náčtuje ihned první hodinu.", "Credits Display Name": "Název kreditů", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Vložte URL vaší instalace PHPmyAdmin. <strong>Bez koncového lomítka!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Vložte URL vaší instalace PHPmyAdmin. <strong>Bez koncového lomítka!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Vložte URL vaší instalace Pterodactyl. <strong>Bez koncového lomítka!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Vložte URL vaší instalace Pterodactyl. <strong>Bez koncového lomítka!</strong>", "Pterodactyl API Key": "Pterodactyl API klíč", "Enter the API Key to your Pterodactyl installation.": "Zadejte API klíč Vaší Pterodactyl instalace.", "Force Discord verification": "Vynutit ověření skrz Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Poukaz může být použit pouze jednou na uživatele. Počet použití upravuje počet různých uživatelů, kteří můžou poukaz použít.", "Max": "Maximum", "Expires at": "Vyprší", - "Used \/ Uses": "Použito", + "Used / Uses": "Použito", "Expires": "Vyprší", "Sign in to start your session": "Pro pokračování se prosím přihlašte", "Password": "Heslo", @@ -386,9 +386,9 @@ "Sync now": "Synchronizovat nyní", "No products available!": "Žádné dostupné balíčky!", "No nodes have been linked!": "Nebyly propojeny žádné uzly!", - "No nests available!": "Žádný dostupný software\/hry!", + "No nests available!": "Žádný dostupný software/hry!", "No eggs have been linked!": "Nebyly nastaveny žádné distribuce!", - "Software \/ Games": "Software\/hry", + "Software / Games": "Software/hry", "Please select software ...": "Prosím zvolte software ...", "---": "----", "Specification ": "Specifikace ", @@ -460,5 +460,7 @@ "tr": "Turečtina", "ru": "Ruština", "sv": "Švédština", - "sk": "Slovensky" + "sk": "Slovensky", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizace/snížení vašeho serveru resetuje váš fakturační cyklus na aktuální. Vaše přeplacené kredity budou vráceny. Cena za nový fakturační cyklus bude odečtena", + "Caution": "Upozornění" } diff --git a/lang/de.json b/lang/de.json index b01d61b70..1e8a8402d 100644 --- a/lang/de.json +++ b/lang/de.json @@ -174,10 +174,10 @@ "please create a file called \"install.lock\" in your dashboard Root directory. Otherwise no settings will be loaded!": "Bitte erstellen Sie eine Datei mit dem Namen \"install.lock\" in Ihrem Dashboard-Root-Verzeichnis. Sonst werden keine Einstellungen geladen!", "or click here": "oder klicke hier", "Company Name": "Firmenname", - "Company Address": "Firmenadresse", + "Company Adress": "Firmenadresse", "Company Phonenumber": "Firmen Telefonnummer", "VAT ID": "Umsatzsteuer-ID", - "Company E-Mail Address": "Firmen E-Mail Adresse", + "Company E-Mail Adress": "Firmen E-Mail Adresse", "Company Website": "Firmenwebseite", "Invoice Prefix": "Rechnungspräfix", "Enable Invoices": "Rechnungen aktivieren", @@ -199,7 +199,7 @@ "Mail Username": "E-Mail Nutzername", "Mail Password": "E-Mail Passwort", "Mail Encryption": "E-Mail Verschlüsselungsart", - "Mail From Address": "Absender E-Mailadresse", + "Mail From Adress": "Absender E-Mailadresse", "Mail From Name": "Absender E-Mailname", "Discord Client-ID": "Discord Client-ID", "Discord Client-Secret": "DIscord Client-Secret", @@ -459,6 +459,8 @@ "zh": "Chinesisch", "tr": "Türkisch", "ru": "Russisch", + "sv": "Schwedisch", + "sk": "Slowakisch", "hourly": "Stündlich", "monthly": "Monatlich", "yearly": "Jährlich", @@ -482,9 +484,6 @@ "Yes, cancel it!": "Ja, löschen!", "No, abort!": "Abbrechen", "Billing period": "Abrechnungsperiode", - "sv": "Schwedisch", - "sk": "Slowakisch", - "Imprint": "Impressum", - "Privacy": "Datenschutz", - "Privacy Policy": "Datenschutzerklärung" + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Das Upgrade/Downgrade Ihres Servers wird Ihre Abrechnungsperiode auf \"jetzt\" zurücksetzen. Ihre überzahlten Credits werden erstattet. Der Preis für die neue Abrechnungsperiode wird abgebucht.", + "Caution": "Achtung" } diff --git a/lang/en.json b/lang/en.json index c0aa64314..07d646083 100644 --- a/lang/en.json +++ b/lang/en.json @@ -10,10 +10,6 @@ "Everything is good!": "Everything is good!", "System settings have not been updated!": "System settings have not been updated!", "System settings updated!": "System settings updated!", - "Discount": "Discount", - "The product you chose can't be purchased with this payment method. The total amount is too small. Please buy a bigger amount or try a different payment method.": "The product you chose can't be purchased with this payment method. The total amount is too small. Please buy a bigger amount or try a different payment method.", - "Tax": "Tax", - "Your payment has been canceled!": "Your payment has been canceled!", "api key created!": "api key created!", "api key updated!": "api key updated!", "api key has been removed!": "api key has been removed!", @@ -24,9 +20,11 @@ "Invoice does not exist on filesystem!": "Invoice does not exist on filesystem!", "unknown": "unknown", "Pterodactyl synced": "Pterodactyl synced", - "An error ocured. Please try again.": "An error ocured. Please try again.", "Your credit balance has been increased!": "Your credit balance has been increased!", - "Unknown user": "Unknown user", + "Your payment is being processed!": "Your payment is being processed!", + "Your payment has been canceled!": "Your payment has been canceled!", + "Payment method": "Payment method", + "Invoice": "Invoice", "Download": "Download", "Product has been created!": "Product has been created!", "Product has been updated!": "Product has been updated!", @@ -36,10 +34,6 @@ "Server removed": "Server removed", "An exception has occurred while trying to remove a resource \"": "An exception has occurred while trying to remove a resource \"", "Server has been updated!": "Server has been updated!", - "renamed": "renamed", - "servers": "servers", - "deleted": "deleted", - "old servers": "old servers", "Unsuspend": "Unsuspend", "Suspend": "Suspend", "Store item has been created!": "Store item has been created!", @@ -75,15 +69,10 @@ "Close": "Close", "Target User already in blacklist. Reason updated": "Target User already in blacklist. Reason updated", "Change Status": "Change Status", - "partner has been created!": "partner has been created!", - "partner has been updated!": "partner has been updated!", - "partner has been removed!": "partner has been removed!", - "Default": "Default", - "Account permanently deleted!": "Account permanently deleted!", "Profile updated": "Profile updated", "Server limit reached!": "Server limit reached!", + "The node '\" . $nodeName . \"' doesn't have the required memory or disk left to allocate this product.": "The node '\" . $nodeName . \"' doesn't have the required memory or disk left to allocate this product.", "You are required to verify your email address before you can create a server.": "You are required to verify your email address before you can create a server.", - "The system administrator has blocked the creation of new servers.": "The system administrator has blocked the creation of new servers.", "You are required to link your discord account before you can create a server.": "You are required to link your discord account before you can create a server.", "Server created": "Server created", "No allocations satisfying the requirements for automatic deployment on this node were found.": "No allocations satisfying the requirements for automatic deployment on this node were found.", @@ -93,7 +82,9 @@ "Not Enough Balance for Upgrade": "Not Enough Balance for Upgrade", "You are required to verify your email address before you can purchase credits.": "You are required to verify your email address before you can purchase credits.", "You are required to link your discord account before you can purchase Credits": "You are required to link your discord account before you can purchase Credits", + "You can't make a ticket because you're on the blacklist for a reason: '\" . $check->reason . \"": "You can't make a ticket because you're on the blacklist for a reason: '\" . $check->reason . \"", "A ticket has been opened, ID: #": "A ticket has been opened, ID: #", + "You can't reply a ticket because you're on the blacklist for a reason: '\" . $check->reason . \"": "You can't reply a ticket because you're on the blacklist for a reason: '\" . $check->reason . \"", "EXPIRED": "EXPIRED", "Payment Confirmation": "Payment Confirmation", "Payment Confirmed!": "Payment Confirmed!", @@ -109,7 +100,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -121,14 +112,12 @@ "Getting started!": "Getting started!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", "If you have any questions, please join our Discord server and #create-a-ticket": "If you have any questions, please join our Discord server and #create-a-ticket", "We hope you can enjoy this hosting experience and if you have any suggestions please let us know": "We hope you can enjoy this hosting experience and if you have any suggestions please let us know", - "Payment method": "Payment method", - "Invoice": "Invoice", "Activity Logs": "Activity Logs", "Dashboard": "Dashboard", "No recent activity from cronjobs": "No recent activity from cronjobs", @@ -153,10 +142,6 @@ "Nodes": "Nodes", "Location": "Location", "Admin Overview": "Admin Overview", - "Version Outdated:": "Version Outdated:", - "You are running on": "You are running on", - "The latest Version is": "The latest Version is", - "Consider updating now": "Consider updating now", "Support server": "Support server", "Documentation": "Documentation", "Github": "Github", @@ -165,59 +150,11 @@ "Total": "Total", "Payments": "Payments", "Pterodactyl": "Pterodactyl", - "Warning!": "Warning!", - "Some nodes got deleted on pterodactyl only. Please click the sync button above.": "Some nodes got deleted on pterodactyl only. Please click the sync button above.", "Resources": "Resources", "Count": "Count", "Locations": "Locations", "Eggs": "Eggs", "Last updated :date": "Last updated :date", - "Latest tickets": "Latest tickets", - "There are no tickets": "There are no tickets", - "Title": "Title", - "User": "User", - "Last updated": "Last updated", - "Controlpanel.gg": "Controlpanel.gg", - "Version": "Version", - "Individual nodes": "Individual nodes", - "You reached the Pterodactyl perPage limit. Please make sure to set it higher than your server count.": "You reached the Pterodactyl perPage limit. Please make sure to set it higher than your server count.", - "You can do that in settings.": "You can do that in settings.", - "Note": "Note", - "If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on ControlPanel. Try clicking the button below.": "If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on ControlPanel. Try clicking the button below.", - "Sync servers": "Sync servers", - "Node": "Node", - "Server count": "Server count", - "Resource usage": "Resource usage", - "Usage": "Usage", - "active": "active", - "total": "total", - "Latest payments": "Latest payments", - "Last month": "Last month", - "Payments in this time window": "Payments in this time window", - "Currency": "Currency", - "Number of payments": "Number of payments", - "Total amount": "Total amount", - "This month": "This month", - "Tax overview": "Tax overview", - "Last year": "Last year", - "Base amount": "Base amount", - "Total taxes": "Total taxes", - "This year": "This year", - "Vouchers": "Vouchers", - "Partner details": "Partner details", - "Partner discount": "Partner discount", - "The discount in percent given to the partner when purchasing credits.": "The discount in percent given to the partner when purchasing credits.", - "Discount in percent": "Discount in percent", - "Registered user discount": "Registered user discount", - "The discount in percent given to all users registered using the partners referral link when purchasing credits.": "The discount in percent given to all users registered using the partners referral link when purchasing credits.", - "Referral system commission": "Referral system commission", - "Override value for referral system commission. You can set it to -1 to get the default commission from settings.": "Override value for referral system commission. You can set it to -1 to get the default commission from settings.", - "Commission in percent": "Commission in percent", - "Partners": "Partners", - "The discount in percent given to the partner at checkout.": "The discount in percent given to the partner at checkout.", - "The discount in percent given to all users registered using the partners referral link.": "The discount in percent given to all users registered using the partners referral link.", - "Created": "Created", - "Actions": "Actions", "Download all Invoices": "Download all Invoices", "Product Price": "Product Price", "Tax Value": "Tax Value", @@ -245,9 +182,6 @@ "Link your products to nodes and eggs to create dynamic pricing for each option": "Link your products to nodes and eggs to create dynamic pricing for each option", "This product will only be available for these nodes": "This product will only be available for these nodes", "This product will only be available for these eggs": "This product will only be available for these eggs", - "No Eggs or Nodes shown?": "No Eggs or Nodes shown?", - "Sync now": "Sync now", - "Min Credits": "Min Credits", "Product": "Product", "CPU": "CPU", "Updated at": "Updated at", @@ -256,9 +190,7 @@ "You usually do not need to change anything here": "You usually do not need to change anything here", "Edit Server": "Edit Server", "Server identifier": "Server identifier", - "Change the server identifier on controlpanel to match a pterodactyl server.": "Change the server identifier on controlpanel to match a pterodactyl server.", - "Server owner": "Server owner", - "Change the current server owner on controlpanel and pterodactyl.": "Change the current server owner on controlpanel and pterodactyl.", + "User": "User", "Server id": "Server id", "Config": "Config", "Suspended at": "Suspended at", @@ -267,10 +199,10 @@ "please create a file called \"install.lock\" in your dashboard Root directory. Otherwise no settings will be loaded!": "please create a file called \"install.lock\" in your dashboard Root directory. Otherwise no settings will be loaded!", "or click here": "or click here", "Company Name": "Company Name", - "Company Address": "Company Address", + "Company Adress": "Company Adress", "Company Phonenumber": "Company Phonenumber", "VAT ID": "VAT ID", - "Company E-Mail Address": "Company E-Mail Address", + "Company E-Mail Adress": "Company E-Mail Adress", "Company Website": "Company Website", "Invoice Prefix": "Invoice Prefix", "Enable Invoices": "Enable Invoices", @@ -280,7 +212,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -292,7 +224,7 @@ "Mail Username": "Mail Username", "Mail Password": "Mail Password", "Mail Encryption": "Mail Encryption", - "Mail From Address": "Mail From Address", + "Mail From Adress": "Mail From Adress", "Mail From Name": "Mail From Name", "Discord Client-ID": "Discord Client-ID", "Discord Client-Secret": "Discord Client-Secret", @@ -303,11 +235,7 @@ "Enable ReCaptcha": "Enable ReCaptcha", "ReCaptcha Site-Key": "ReCaptcha Site-Key", "ReCaptcha Secret-Key": "ReCaptcha Secret-Key", - "Your Recaptcha": "Your Recaptcha", - "Referral System": "Referral System", "Enable Referral": "Enable Referral", - "Always give commission": "Always give commission", - "Should users recieve the commission only for the first payment, or for every payment?": "Should users recieve the commission only for the first payment, or for every payment?", "Mode": "Mode", "Should a reward be given if a new User registers or if a new user buys credits": "Should a reward be given if a new User registers or if a new user buys credits", "Commission": "Commission", @@ -323,10 +251,6 @@ "Everyone": "Everyone", "Clients": "Clients", "Enable Ticketsystem": "Enable Ticketsystem", - "Notify on Ticket creation": "Notify on Ticket creation", - "Who will receive an E-Mail when a new Ticket is created": "Who will receive an E-Mail when a new Ticket is created", - "Admins": "Admins", - "Moderators": "Moderators", "PayPal Client-ID": "PayPal Client-ID", "PayPal Secret-Key": "PayPal Secret-Key", "PayPal Sandbox Client-ID": "PayPal Sandbox Client-ID", @@ -339,20 +263,15 @@ "Payment Methods": "Payment Methods", "Tax Value in %": "Tax Value in %", "System": "System", - "Show Terms of Service": "Show Terms of Service", - "Show Imprint": "Show Imprint", - "Show Privacy Policy": "Show Privacy Policy", "Register IP Check": "Register IP Check", "Prevent users from making multiple accounts using the same IP address.": "Prevent users from making multiple accounts using the same IP address.", "Charge first hour at creation": "Charge first hour at creation", "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", - "Pterodactyl API perPage limit": "Pterodactyl API perPage limit", - "The Pterodactyl API perPage limit. It is necessary to set it higher than your server count.": "The Pterodactyl API perPage limit. It is necessary to set it higher than your server count.", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Pterodactyl Admin-Account API Key": "Pterodactyl Admin-Account API Key", @@ -360,8 +279,6 @@ "Test API": "Test API", "Force Discord verification": "Force Discord verification", "Force E-Mail verification": "Force E-Mail verification", - "Creation of new users": "Creation of new users", - "If unchecked, it will disable the registration of new users in the system, and this will also apply to the API.": "If unchecked, it will disable the registration of new users in the system, and this will also apply to the API.", "Initial Credits": "Initial Credits", "Initial Server Limit": "Initial Server Limit", "Credits Reward Amount - Discord": "Credits Reward Amount - Discord", @@ -370,38 +287,13 @@ "Server Limit Increase - E-Mail": "Server Limit Increase - E-Mail", "Server Limit after Credits Purchase": "Server Limit after Credits Purchase", "Server": "Server", - "Enable upgrade\/downgrade of servers": "Enable upgrade\/downgrade of servers", - "Allow upgrade\/downgrade to a new product for the given server": "Allow upgrade\/downgrade to a new product for the given server", - "Creation of new servers": "Creation of new servers", - "If unchecked, it will disable the creation of new servers for regular users and system moderators, this has no effect for administrators.": "If unchecked, it will disable the creation of new servers for regular users and system moderators, this has no effect for administrators.", "Server Allocation Limit": "Server Allocation Limit", "The maximum amount of allocations to pull per node for automatic deployment, if more allocations are being used than this limit is set to, no new servers can be created!": "The maximum amount of allocations to pull per node for automatic deployment, if more allocations are being used than this limit is set to, no new servers can be created!", - "Minimum credits": "Minimum credits", - "The minimum amount of credits user has to have to create a server. Can be overridden by package limits.": "The minimum amount of credits user has to have to create a server. Can be overridden by package limits.", - "SEO": "SEO", - "SEO Title": "SEO Title", - "An SEO title tag must contain your target keyword. This tells both Google and searchers that your web page is relevant to this search query!": "An SEO title tag must contain your target keyword. This tells both Google and searchers that your web page is relevant to this search query!", - "SEO Description": "SEO Description", - "The SEO site description represents your homepage. Search engines show this description in search results for your homepage if they dont find content more relevant to a visitors search terms.": "The SEO site description represents your homepage. Search engines show this description in search results for your homepage if they dont find content more relevant to a visitors search terms.", "Design": "Design", - "Theme": "Theme", "Enable Logo on Loginpage": "Enable Logo on Loginpage", "Select panel icon": "Select panel icon", "Select Login-page Logo": "Select Login-page Logo", "Select panel favicon": "Select panel favicon", - "Enable the Alert Message on Homepage": "Enable the Alert Message on Homepage", - "Alert Color": "Alert Color", - "Blue": "Blue", - "Grey": "Grey", - "Green": "Green", - "Red": "Red", - "Orange": "Orange", - "Cyan": "Cyan", - "Alert Message (HTML might be used)": "Alert Message (HTML might be used)", - "Message of the day": "Message of the day", - "Enable the MOTD on the Homepage": "Enable the MOTD on the Homepage", - "Enable the Useful-Links section": "Enable the Useful-Links section", - "MOTD-Text": "MOTD-Text", "Store": "Store", "Server Slots": "Server Slots", "Currency code": "Currency code", @@ -417,6 +309,7 @@ "Useful Links": "Useful Links", "Icon class name": "Icon class name", "You can find available free icons": "You can find available free icons", + "Title": "Title", "Link": "Link", "description": "description", "Icon": "Icon", @@ -446,9 +339,11 @@ "Content": "Content", "Server limit": "Server limit", "Discord": "Discord", + "Usage": "Usage", "IP": "IP", "Referals": "Referals", "referral-code": "referral-code", + "Vouchers": "Vouchers", "Voucher details": "Voucher details", "Summer break voucher": "Summer break voucher", "Code": "Code", @@ -457,18 +352,14 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", - "Email or Username": "Email or Username", "Password": "Password", "Remember Me": "Remember Me", "Sign In": "Sign In", "Forgot Your Password?": "Forgot Your Password?", "Register a new membership": "Register a new membership", - "Imprint": "Imprint", - "Privacy": "Privacy", - "Terms of Service": "Terms of Service", "Please confirm your password before continuing.": "Please confirm your password before continuing.", "You forgot your password? Here you can easily retrieve a new password.": "You forgot your password? Here you can easily retrieve a new password.", "Request new password": "Request new password", @@ -476,10 +367,7 @@ "You are only one step a way from your new password, recover your password now.": "You are only one step a way from your new password, recover your password now.", "Retype password": "Retype password", "Change password": "Change password", - "The system administrator has blocked the registration of new users": "The system administrator has blocked the registration of new users", - "Back": "Back", "Referral code": "Referral code", - "I agree to the": "I agree to the", "Register": "Register", "I already have a membership": "I already have a membership", "Verify Your Email Address": "Verify Your Email Address", @@ -489,17 +377,6 @@ "click here to request another": "click here to request another", "per month": "per month", "Out of Credits in": "Out of Credits in", - "Partner program": "Partner program", - "Your referral URL": "Your referral URL", - "Click to copy": "Click to copy", - "Number of referred users:": "Number of referred users:", - "Your discount": "Your discount", - "Discount for your new users": "Discount for your new users", - "Reward per registered user": "Reward per registered user", - "New user payment commision": "New user payment commision", - "Make a purchase to reveal your referral-URL": "Make a purchase to reveal your referral-URL", - "URL copied to clipboard": "URL copied to clipboard", - "Privacy Policy": "Privacy Policy", "Home": "Home", "Language": "Language", "See all Notifications": "See all Notifications", @@ -517,6 +394,7 @@ "Management": "Management", "Other": "Other", "Logs": "Logs", + "Warning!": "Warning!", "You have not yet verified your email address": "You have not yet verified your email address", "Click here to resend verification email": "Click here to resend verification email", "Please contact support If you didnt receive your verification email.": "Please contact support If you didnt receive your verification email.", @@ -528,12 +406,12 @@ "Blacklist List": "Blacklist List", "Reason": "Reason", "Created At": "Created At", + "Actions": "Actions", "Add To Blacklist": "Add To Blacklist", "please make the best of it": "please make the best of it", - "Please note, the blacklist will make the user unable to make a ticket\/reply again": "Please note, the blacklist will make the user unable to make a ticket\/reply again", + "Please note, the blacklist will make the user unable to make a ticket/reply again": "Please note, the blacklist will make the user unable to make a ticket/reply again", "Ticket": "Ticket", "Category": "Category", - "Priority": "Priority", "Last Updated": "Last Updated", "Comment": "Comment", "All notifications": "All notifications", @@ -544,7 +422,6 @@ "Please contact support If you face any issues.": "Please contact support If you face any issues.", "Due to system settings you are required to verify your discord account!": "Due to system settings you are required to verify your discord account!", "It looks like this hasnt been set-up correctly! Please contact support.": "It looks like this hasnt been set-up correctly! Please contact support.", - "Permanently delete my account": "Permanently delete my account", "Change Password": "Change Password", "Current Password": "Current Password", "Link your discord account!": "Link your discord account!", @@ -553,30 +430,25 @@ "You are verified!": "You are verified!", "Re-Sync Discord": "Re-Sync Discord", "Save Changes": "Save Changes", - "Are you sure you want to permanently delete your account and all of your servers?": "Are you sure you want to permanently delete your account and all of your servers?", - "Delete my account": "Delete my account", - "Account has been destroyed": "Account has been destroyed", - "Account was NOT deleted.": "Account was NOT deleted.", + "URL copied to clipboard": "URL copied to clipboard", "Server configuration": "Server configuration", - "here": "here", "Make sure to link your products to nodes and eggs.": "Make sure to link your products to nodes and eggs.", "There has to be at least 1 valid product for server creation": "There has to be at least 1 valid product for server creation", + "Sync now": "Sync now", "No products available!": "No products available!", "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", + "Node": "Node", "Resource Data:": "Resource Data:", "vCores": "vCores", "MB": "MB", "MySQL": "MySQL", "ports": "ports", - "Required": "Required", - "to create this server": "to create this server", - "Server cant fit on this Node": "Server cant fit on this Node", "Not enough": "Not enough", "Create server": "Create server", "Please select a node ...": "Please select a node ...", @@ -600,20 +472,20 @@ "Hourly Price": "Hourly Price", "Monthly Price": "Monthly Price", "MySQL Database": "MySQL Database", - "Upgrade \/ Downgrade": "Upgrade \/ Downgrade", - "Upgrade\/Downgrade Server": "Upgrade\/Downgrade Server", + "To enable the upgrade/downgrade system, please set your Ptero Admin-User API Key in the Settings!": "To enable the upgrade/downgrade system, please set your Ptero Admin-User API Key in the Settings!", + "Upgrade / Downgrade": "Upgrade / Downgrade", + "Upgrade/Downgrade Server": "Upgrade/Downgrade Server", "FOR DOWNGRADE PLEASE CHOOSE A PLAN BELOW YOUR PLAN": "FOR DOWNGRADE PLEASE CHOOSE A PLAN BELOW YOUR PLAN", "YOUR PRODUCT": "YOUR PRODUCT", "Select the product": "Select the product", - "Server can´t fit on this node": "Server can´t fit on this node", "Once the Upgrade button is pressed, we will automatically deduct the amount for the first hour according to the new product from your credits": "Once the Upgrade button is pressed, we will automatically deduct the amount for the first hour according to the new product from your credits", - "Server will be automatically restarted once upgraded": "Server will be automatically restarted once upgraded", "Change Product": "Change Product", "Delete Server": "Delete Server", "This is an irreversible action, all files of this server will be removed!": "This is an irreversible action, all files of this server will be removed!", "Date": "Date", "Subtotal": "Subtotal", "Amount Due": "Amount Due", + "Tax": "Tax", "Submit Payment": "Submit Payment", "Purchase": "Purchase", "There are no store products!": "There are no store products!", @@ -632,10 +504,13 @@ "VAT Code": "VAT Code", "Phone": "Phone", "Units": "Units", + "Discount": "Discount", "Total discount": "Total discount", "Taxable amount": "Taxable amount", "Tax rate": "Tax rate", + "Total taxes": "Total taxes", "Shipping": "Shipping", + "Total amount": "Total amount", "Notes": "Notes", "Amount in words": "Amount in words", "Please pay until": "Please pay until", @@ -653,5 +528,30 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" -} \ No newline at end of file + "hu": "Hungarian", + "hourly": "Hourly", + "monthly": "Monthly", + "yearly": "Yearly", + "daily": "Daily", + "weekly": "Weekly", + "quarterly": "Quarterly", + "half-annually": "Half-annually", + "annually": "Annually", + "Suspended": "Suspended", + "Cancelled": "Cancelled", + "An exception has occurred while trying to cancel the server": "An exception has occurred while trying to cancel the server", + "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.", + "Cancel Server?": "Cancel Server?", + "Delete Server?": "Delete Server?", + "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.": "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.", + "Billing Period": "Billing Period", + "Next Billing Cycle": "Next Billing Cycle", + "Manage Server": "Manage Server", + "Delete Server": "Delete Server", + "Cancel Server": "Cancel Server", + "Yes, cancel it!": "Yes, cancel it!", + "No, abort!": "No, abort!", + "Billing period": "Billing period", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution" +} diff --git a/lang/es.json b/lang/es.json index 400a46f49..818d8e132 100644 --- a/lang/es.json +++ b/lang/es.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Error de creación del servidor", "Your servers have been suspended!": "¡Sus servidores han sido suspendidos!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Para volver a habilitar automáticamente sus servidores, debe comprar más créditos.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Para volver a habilitar automáticamente sus servidores, debe comprar más créditos.", "Purchase credits": "Comprar Créditos", "If you have any questions please let us know.": "Si tienes más preguntas, por favor háznoslas saber.", "Regards": "Atentamente", @@ -93,7 +93,7 @@ "Getting started!": "¡Empezando!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -174,10 +174,10 @@ "please create a file called \"install.lock\" in your dashboard Root directory. Otherwise no settings will be loaded!": "cree un archivo llamado \"install.lock\" en el directorio Raíz de su tablero. ¡De lo contrario, no se cargará ninguna configuración!", "or click here": "o haga clic aquí", "Company Name": "Nombre Empresa", - "Company Address": "Dirección de la Empresa", + "Company Adress": "Dirección de la Empresa", "Company Phonenumber": "Número de teléfono de la empresa", "VAT ID": "ID de IVA", - "Company E-Mail Address": "Dirección de correo electrónico de la empresa", + "Company E-Mail Adress": "Dirección de correo electrónico de la empresa", "Company Website": "Página Web de la empresa", "Invoice Prefix": "Prefijo de factura", "Enable Invoices": "Habilitar facturas", @@ -187,7 +187,7 @@ "Default language": "Idioma predeterminado", "The fallback Language, if something goes wrong": "El lenguaje alternativo, si algo sale mal", "Datable language": "Lenguaje de tabla de datos", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "El código de idioma de las tablas de datos. <br><strong>Ejemplo:<\/strong> en-gb, fr_fr, de_de<br>Más información: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "El código de idioma de las tablas de datos. <br><strong>Ejemplo:</strong> en-gb, fr_fr, de_de<br>Más información: ", "Auto-translate": "Traducir automáticamente", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Si está marcado, el Tablero se traducirá solo al idioma del Cliente, si está disponible", "Client Language-Switch": "Cambio de idioma del cliente", @@ -199,7 +199,7 @@ "Mail Username": "Nombre de usuario del correo", "Mail Password": "Contraseña de correo", "Mail Encryption": "Cifrado de correo", - "Mail From Address": "Dirección del correo", + "Mail From Adress": "Dirección del correo", "Mail From Name": "Nombre del correo", "Discord Client-ID": "Discord ID-Cliente", "Discord Client-Secret": "Discord Secreto-Cliente", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Carga la primera hora de créditos al crear un servidor.", "Credits Display Name": "Nombre de los Créditos para mostrar", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Ingrese la URL de su instalación de PHPMyAdmin. <strong>¡Sin una barra diagonal final!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Ingrese la URL de su instalación de PHPMyAdmin. <strong>¡Sin una barra diagonal final!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Introduzca la URL de su instalación de Pterodactyl. <strong>¡Sin una barra diagonal final!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Introduzca la URL de su instalación de Pterodactyl. <strong>¡Sin una barra diagonal final!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Ingrese la API Key para su instalación de Pterodactyl.", "Force Discord verification": "Forzar verificación de Discord", @@ -263,7 +263,7 @@ "Select panel favicon": "Seleccionar favicon del panel", "Store": "Tienda", "Server Slots": "Server Slots", - "Currency code": "Código de divisa\/moneda", + "Currency code": "Código de divisa/moneda", "Checkout the paypal docs to select the appropriate code": "Consulte los documentos de PayPal para seleccionar el código apropiado", "Quantity": "Cantidad", "Amount given to the user after purchasing": "Importe dado al usuario después de la compra", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "El descuento solo se puede utilizar una vez por usuario. Los usos especifica el número de usuarios diferentes que pueden utilizar este cupón.", "Max": "Máx", "Expires at": "Expira el", - "Used \/ Uses": "Uso \/ Usos", + "Used / Uses": "Uso / Usos", "Expires": "Expira", "Sign in to start your session": "Iniciar sesión para comenzar", "Password": "Contraseña", @@ -388,7 +388,7 @@ "No nodes have been linked!": "¡No se han vinculado nodos!", "No nests available!": "¡No hay nidos disponibles!", "No eggs have been linked!": "¡No se han vinculado huevos!", - "Software \/ Games": "Software \/ Juegos", + "Software / Games": "Software / Juegos", "Please select software ...": "Seleccione el software...", "---": "---", "Specification ": "Especificación ", @@ -460,5 +460,7 @@ "tr": "Turco", "ru": "Ruso", "sv": "Sueco", - "sk": "Eslovaco" + "sk": "Eslovaco", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Actualizar/Reducir el servidor restablecerá su ciclo de facturación a ahora. Se reembolsarán los créditos sobrepagados. El precio del nuevo ciclo de facturación se retirará", + "Caution": "Cuidado" } diff --git a/lang/fr.json b/lang/fr.json index f415e5c62..65a6bc44c 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Erreur lors de la création de votre serveur", "Your servers have been suspended!": "Votre serveur à été suspendu !", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Pour réactiver automatiquement votre ou vos serveurs, vous devez racheter des crédits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Pour réactiver automatiquement votre ou vos serveurs, vous devez racheter des crédits.", "Purchase credits": "Acheter des crédits", "If you have any questions please let us know.": "N'hésitez pas à nous contacter si vous avez des questions.", "Regards": "Cordialement", @@ -93,7 +93,7 @@ "Getting started!": "Commencer !", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Langue par défaut", "The fallback Language, if something goes wrong": "La langue de repli, si quelque chose ne va pas", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Le code ne peut être utilisé qu'une seule fois. L'utilisation spécifie le nombre d'utilisateurs différents qui peuvent utiliser ce code.", "Max": "Max", "Expires at": "Expire à", - "Used \/ Uses": "Utilisé \/ Utilisations", + "Used / Uses": "Utilisé / Utilisations", "Expires": "Expire", "Sign in to start your session": "Identifiez-vous pour commencer votre session", "Password": "Mot de passe", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Aucune node n'a été lié !", "No nests available!": "Aucun nests disponible !", "No eggs have been linked!": "Aucun eggs n'a été lié !", - "Software \/ Games": "Logiciels \/ Jeux", + "Software / Games": "Logiciels / Jeux", "Please select software ...": "Veuillez sélectionner...", "---": "---", "Specification ": "Spécification ", @@ -447,6 +447,8 @@ "Notes": "Notes", "Amount in words": "Montant en toutes lettres", "Please pay until": "Veuillez payer avant", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Mettre à niveau / Réduire votre serveur réinitialisera votre cycle de facturation à maintenant. Vos crédits surpayés seront remboursés. Le prix du nouveau cycle de facturation sera débité", + "Caution": "Attention", "cs": "Czech", "de": "German", "en": "English", diff --git a/lang/he.json b/lang/he.json index a1524d6fb..a64fde04a 100644 --- a/lang/he.json +++ b/lang/he.json @@ -60,8 +60,8 @@ "You ran out of Credits": "נגמר לך המטבעות", "Profile updated": "הפרופיל עודכן", "Server limit reached!": "הגעת להגבלת השרתים!", - "You are required to verify your email address before you can create a server.": "אתה מדרש לאמת את כתובת המייל שלך לפני שתוכל\/י ליצור שרת", - "You are required to link your discord account before you can create a server.": "אתה חייב לקשר את החשבון דיסקורד שלך לפני שתוכל\/י ליצור שרת", + "You are required to verify your email address before you can create a server.": "אתה מדרש לאמת את כתובת המייל שלך לפני שתוכל/י ליצור שרת", + "You are required to link your discord account before you can create a server.": "אתה חייב לקשר את החשבון דיסקורד שלך לפני שתוכל/י ליצור שרת", "Server created": "השרת נוצר", "No allocations satisfying the requirements for automatic deployment on this node were found.": "לא נמצאו הקצאות העומדות בדרישות לפריסה אוטומטית בשרת זה.", "You are required to verify your email address before you can purchase credits.": "אתה נדרש לאמת את כתובת המייל שלך לפני רכישת מטבעות", @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "שגיאה ביצירת שרת", "Your servers have been suspended!": "השרת שלך מושעה!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "כדי להפעיל מחדש את השרתים שלך באופן אוטומטי, עליך לרכוש מטבעות נוספות.", + "To automatically re-enable your server/s, you need to purchase more credits.": "כדי להפעיל מחדש את השרתים שלך באופן אוטומטי, עליך לרכוש מטבעות נוספות.", "Purchase credits": "לרכישת מטבעות", "If you have any questions please let us know.": "אם יש לכם כל שאלה תיידעו אותנו", "Regards": "בברכה", @@ -93,7 +93,7 @@ "Getting started!": "מתחילים!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -125,7 +125,7 @@ "Admin Overview": "סקירת מנהל", "Support server": "שרת תמיכה", "Documentation": "מדריך", - "Github": "Github\/גיטאהב", + "Github": "Github/גיטאהב", "Support ControlPanel": "תמיכת ControlPanel", "Servers": "שרתים", "Total": "בסך הכל", @@ -156,7 +156,7 @@ "Minimum": "מינימום", "Setting to -1 will use the value from configuration.": "הגדרות ל -1 ישתמש בערך מ configuration.", "IO": "IO", - "Databases": "Databases\/ממסד נתונים", + "Databases": "Databases/ממסד נתונים", "Backups": "גיבויים", "Allocations": "הקצאות", "Product Linking": "מוצרים מקושרים", @@ -174,10 +174,10 @@ "please create a file called \"install.lock\" in your dashboard Root directory. Otherwise no settings will be loaded!": "אנא צור קובץ בשם \"install.lock\" בספריית השורש של dashboard שלך. אחרת לא ייטענו הגדרות!", "or click here": "או לחץ כאן", "Company Name": "שם החברה", - "Company Address": "כתובת החברה", + "Company Adress": "כתובת החברה", "Company Phonenumber": "מספר טלפון של החברה", "VAT ID": "מזהה מעמ", - "Company E-Mail Address": "כתובת מייל של החברה", + "Company E-Mail Adress": "כתובת מייל של החברה", "Company Website": "אתר החברה", "Invoice Prefix": "קידומת החשבונים", "Enable Invoices": "אפשר חשבוניות", @@ -187,7 +187,7 @@ "Default language": "שפת ברירת מחדל", "The fallback Language, if something goes wrong": "אם משהו משתבש", "Datable language": "שפה ניתנת לנתונים", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "טבלת הנתונים של השפות.<br><strong>לדוגמא:<\/strong> en-gb, fr_fr, de_de<br>למידע נוסף: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "טבלת הנתונים של השפות.<br><strong>לדוגמא:</strong> en-gb, fr_fr, de_de<br>למידע נוסף: ", "Auto-translate": "תרגום אוטומטי", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "אם זה מסומן, Dashboard יתרגם את עצמו לשפת הלקוח, אם זמין", "Client Language-Switch": "החלפת שפת לקוח", @@ -199,7 +199,7 @@ "Mail Username": "שם המייל", "Mail Password": "סיסמת המייל", "Mail Encryption": "הצפנת המייל", - "Mail From Address": "מייל מכתובת", + "Mail From Adress": "מייל מכתובת", "Mail From Name": "מייל משם", "Discord Client-ID": "מזהה של בוט דיסקורד", "Discord Client-Secret": "מזהה סודי של בוט דיסקורד", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Cגובה מטבעות בשווי השעה הראשונה בעת יצירת שרת.", "Credits Display Name": "שם המטבעות", "PHPMyAdmin URL": "קישור PHPMyAdmin", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "הכנס את הקישור to פיחפי. <strong>בלי צלייה נגררת!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "הכנס את הקישור to פיחפי. <strong>בלי צלייה נגררת!</strong>", "Pterodactyl URL": "קישור Pterodactyl", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API מפתח", "Enter the API Key to your Pterodactyl installation.": "הכנס את מפתח ה API Pterodactyl installation.", "Force Discord verification": "אימות דיסקורד חובה", @@ -300,12 +300,12 @@ "Notifications": "התראות", "All": "הכל", "Send via": "שליחה באמצאות", - "Database": "Database\/מאגר נתונים", + "Database": "Database/מאגר נתונים", "Content": "קשר", "Server limit": "הגבלת שרת", - "Discord": "Discord\/דיסקורד", + "Discord": "Discord/דיסקורד", "Usage": "נוהג", - "IP": "IP\/אייפי", + "IP": "IP/אייפי", "Referals": "Referals", "Vouchers": "קופונים", "Voucher details": "פרטי קופון", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "ניתן להשתמש בשובר פעם אחת בלבד לכל משתמש. שימושים מציינים את מספר המשתמשים השונים שיכולים להשתמש בשובר זה.", "Max": "מקסימום", "Expires at": "יפוג ב", - "Used \/ Uses": "משומש \/ שימושים", + "Used / Uses": "משומש / שימושים", "Expires": "פגי תוקף", "Sign in to start your session": "התחבר על מנת להתחיל", "Password": "סיסמה", @@ -339,7 +339,7 @@ "Before proceeding, please check your email for a verification link.": "לפני שתמשיך, אנא בדוק באימייל שלך קישור לאימות.", "If you did not receive the email": "אם לא קיבלת את המייל", "click here to request another": "לחץ כאן כדי לבקש אחר", - "per month": "\/חודש", + "per month": "/חודש", "Out of Credits in": "נגמרו המטבעות ב", "Home": "בית", "Language": "שפה", @@ -388,7 +388,7 @@ "No nodes have been linked!": "שרתים לא מקושרים!", "No nests available!": "No nests available!", "No eggs have been linked!": "אין eggs מקושרים", - "Software \/ Games": "תוכנה \/ משחקים", + "Software / Games": "תוכנה / משחקים", "Please select software ...": "בבקשה תחבר תוכנה ...", "---": "---", "Specification ": "ציין ", @@ -411,9 +411,9 @@ "Specification": "לציין", "Resource plan": "תוכנית משאבים", "RAM": "RAM", - "MySQL Databases": "בסיס הנתונים <bdi dir=\"ltr\">MySQL<\/bdi>", - "per Hour": "\/שעה", - "per Month": "\/חודש", + "MySQL Databases": "בסיס הנתונים <bdi dir=\"ltr\">MySQL</bdi>", + "per Hour": "/שעה", + "per Month": "/חודש", "Manage": "לנהל", "Are you sure?": "האם אתה בטוח?", "This is an irreversible action, all files of this server will be removed.": "זוהי פעולה בלתי הפיכה, כל הקבצים של שרת זה יוסרו.", @@ -460,5 +460,7 @@ "tr": "טורקית", "ru": "רוסית", "sv": "שוודית", - "sk": "סלובקית" + "sk": "סלובקית", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "שדרוג / הורדת שרת יאפס את מחזור החיוב שלך לעכשיו. הקרדיטים ששילמת יוחזרו. המחיר למחזור החיוב החדש יוחסם", + "Caution": "אזהרה" } diff --git a/lang/hi.json b/lang/hi.json index e7b950b14..1449e92af 100644 --- a/lang/hi.json +++ b/lang/hi.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "सर्वर निर्माण त्रुटि", "Your servers have been suspended!": "आपके सर्वर निलंबित कर दिए गए हैं!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "अपने सर्वर\/सर्वर को स्वचालित रूप से पुन: सक्षम करने के लिए, आपको अधिक क्रेडिट खरीदने की आवश्यकता है।", + "To automatically re-enable your server/s, you need to purchase more credits.": "अपने सर्वर/सर्वर को स्वचालित रूप से पुन: सक्षम करने के लिए, आपको अधिक क्रेडिट खरीदने की आवश्यकता है।", "Purchase credits": "क्रेडिट खरीदें", "If you have any questions please let us know.": "यदि आपके पास कोई प्रश्न है, तो हमें बताएं।", "Regards": "सादर", @@ -93,7 +93,7 @@ "Getting started!": "शुरू करना!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "डिफ़ॉल्ट भाषा", "The fallback Language, if something goes wrong": "फ़ॉलबैक भाषा, अगर कुछ गलत हो जाता है", "Datable language": "डेटा योग्य भाषा", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "डेटाटेबल्स लैंग-कोड। <br><strong>उदाहरण:<\/strong> en-gb, fr_fr, de_de<br>अधिक जानकारी: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "डेटाटेबल्स लैंग-कोड। <br><strong>उदाहरण:</strong> en-gb, fr_fr, de_de<br>अधिक जानकारी: ", "Auto-translate": "ऑटो का अनुवाद", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "यदि यह चेक किया जाता है, तो डैशबोर्ड स्वयं को क्लाइंट भाषा में अनुवाद करेगा, यदि उपलब्ध हो", "Client Language-Switch": "क्लाइंट भाषा-स्विच", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "सर्वर बनाने पर पहले घंटे के क्रेडिट का शुल्क लेता है।", "Credits Display Name": "क्रेडिट प्रदर्शन नाम", "PHPMyAdmin URL": "PhpMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "अपने PHPMyAdmin इंस्टॉलेशन का URL दर्ज करें। <strong>पिछली स्लैश के बिना!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "अपने PHPMyAdmin इंस्टॉलेशन का URL दर्ज करें। <strong>पिछली स्लैश के बिना!</strong>", "Pterodactyl URL": "पटरोडैक्टाइल यूआरएल", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "अपने Pterodactyl संस्थापन का URL दर्ज करें। <strong>पिछली स्लैश के बिना!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "अपने Pterodactyl संस्थापन का URL दर्ज करें। <strong>पिछली स्लैश के बिना!</strong>", "Pterodactyl API Key": "पटरोडैक्टाइल एपीआई कुंजी", "Enter the API Key to your Pterodactyl installation.": "अपने Pterodactyl स्थापना के लिए API कुंजी दर्ज करें।", "Force Discord verification": "बल विवाद सत्यापन", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "वाउचर प्रति उपयोगकर्ता केवल एक बार उपयोग किया जा सकता है। उपयोग इस वाउचर का उपयोग करने वाले विभिन्न उपयोगकर्ताओं की संख्या को निर्दिष्ट करता है।", "Max": "मैक्स", "Expires at": "पर समाप्त हो रहा है", - "Used \/ Uses": "प्रयुक्त \/ उपयोग", + "Used / Uses": "प्रयुक्त / उपयोग", "Expires": "समय-सीमा समाप्त", "Sign in to start your session": "अपना सत्र शुरू करने के लिए साइन इन करें", "Password": "पासवर्ड", @@ -388,7 +388,7 @@ "No nodes have been linked!": "कोई नोड लिंक नहीं किया गया है!", "No nests available!": "कोई घोंसला उपलब्ध नहीं है!", "No eggs have been linked!": "कोई अंडे नहीं जोड़े गए हैं!", - "Software \/ Games": "सॉफ्टवेयर \/ खेल", + "Software / Games": "सॉफ्टवेयर / खेल", "Please select software ...": "कृपया सॉफ्टवेयर चुनें...", "---": "---", "Specification ": "विनिर्देश", @@ -447,6 +447,8 @@ "Notes": "टिप्पणियाँ", "Amount in words": "राशि शब्दों में", "Please pay until": "कृपया भुगतान करें", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "अपने सर्वर को अपग्रेड / डाउनग्रेड करने से आपका बिलिंग साइकिल अब तक रीसेट हो जाएगा। आपके ओवरपेड क्रेडिट वापस किया जाएगा। नए बिलिंग साइकिल के लिए की गई मूल्य निकाला जाएगा", + "Caution": "सावधान", "cs": "चेक", "de": "जर्मन", "en": "अंग्रेज़ी", diff --git a/lang/hu.json b/lang/hu.json index 744fcf0c3..30350eacf 100644 --- a/lang/hu.json +++ b/lang/hu.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Valaki regisztrált a te Kódoddal!", "Server Creation Error": "Hiba a szerver készítése közben", "Your servers have been suspended!": "A szervered fel lett függesztve!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "A szervered\/szervereid autómatikus újraengedélyezéséhez Kreditet kell vásárolnod.", + "To automatically re-enable your server/s, you need to purchase more credits.": "A szervered/szervereid autómatikus újraengedélyezéséhez Kreditet kell vásárolnod.", "Purchase credits": "Kreditek vásárlása", "If you have any questions please let us know.": "Ha kérdésed van, kérjük fordulj hozzánk.", "Regards": "Üdvözlettel", @@ -93,7 +93,7 @@ "Getting started!": "Kezdhetjük!", "Welcome to our dashboard": "Üdvözlünk az Irányítópultban", "Verification": "Hitelesítés", - "You can verify your e-mail address and link\/verify your Discord account.": "Hitelesíteni tudod az email címedet és a Discord fiókodat.", + "You can verify your e-mail address and link/verify your Discord account.": "Hitelesíteni tudod az email címedet és a Discord fiókodat.", "Information": "Információk", "This dashboard can be used to create and delete servers": "Ebben az Irányítópultban szervereket tudsz létrehozni és törölni", "These servers can be used and managed on our pterodactyl panel": "Ezeket a szervereket a Pterodactyl panelben tudod kezelni", @@ -187,7 +187,7 @@ "Default language": "Alapértelmezett nyelv", "The fallback Language, if something goes wrong": "A tartalék nyelv, ha bármi rosszul működne", "Datable language": "Keltezhető nyelv", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Az adattáblák kódnyelve. <br><strong>Például:<\/strong> en-gb, fr_fr, de_de<br>Több információ: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Az adattáblák kódnyelve. <br><strong>Például:</strong> en-gb, fr_fr, de_de<br>Több információ: ", "Auto-translate": "Autómatikus fordítás", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Ha be van kapcsolva, akkor az Irányítópult autómatikusan le lesz fordítva a Kliens által használt nyelvre, ha az elérhető", "Client Language-Switch": "Kliens nyelv váltás", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Első óra kifizetése szerver létrehozásnál.", "Credits Display Name": "Kredit megnevezése", "PHPMyAdmin URL": "PHPMyAdmin Hivatkozás", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Írd be a hivatkozást a PHPMyAdmin telepítéséhez. <strong>A zárjó perjel nélkül!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Írd be a hivatkozást a PHPMyAdmin telepítéséhez. <strong>A zárjó perjel nélkül!</strong>", "Pterodactyl URL": "Pterodactyl Hivatkozás", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Írd be a hivatkozást a Pterodactyl telepítéséhez. <strong>A zárjó perjel nélkül!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Írd be a hivatkozást a Pterodactyl telepítéséhez. <strong>A zárjó perjel nélkül!</strong>", "Pterodactyl API Key": "Pterodactyl API Kulcs", "Enter the API Key to your Pterodactyl installation.": "Írd be az API Kulcsot a Pterodactyl telepítéséhez.", "Force Discord verification": "Discord hitelesítés kötelezése", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Az utalványt egy felhasználó csak egyszer használhatja fel. Ezzel meg tudod adni, hogy hány felhasználó tudja felhasználni az utalványt.", "Max": "Max", "Expires at": "Lejárás ideje", - "Used \/ Uses": "Felhasználva \/ Felhasználások száma", + "Used / Uses": "Felhasználva / Felhasználások száma", "Expires": "Lejár", "Sign in to start your session": "Jelentkezz be a munkamenet elindításához", "Password": "Jelszó", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Nem lett csomópont hozzácsatolva!", "No nests available!": "Nincs elérhető fészek!", "No eggs have been linked!": "Nem lett tojás hozzácsatolva!", - "Software \/ Games": "Szoftver \/ Játékok", + "Software / Games": "Szoftver / Játékok", "Please select software ...": "Szoftver kiválasztása ...", "---": "---", "Specification ": "Specifikációk ", @@ -447,6 +447,8 @@ "Notes": "Jegyzetek", "Amount in words": "Mennyiség szavakban", "Please pay until": "Fizetési határidő", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "A szerver frissítése / lefrissítése visszaállítja a számlázási ciklust az aktuálisra. A túlfizetett Kreditet visszatérítjük. A számlázási ciklus új ára lesz kivonva", + "Caution": "Figyelem", "cs": "Cseh", "de": "Német", "en": "Angol", diff --git a/lang/it.json b/lang/it.json index 5bc51c875..ac2f1020f 100644 --- a/lang/it.json +++ b/lang/it.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Errore di creazione del server", "Your servers have been suspended!": "I tuoi server sono stati sospesi!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Per ri-abilitare i tuoi server automaticamente, devi acquistare più crediti.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Per ri-abilitare i tuoi server automaticamente, devi acquistare più crediti.", "Purchase credits": "Acquista crediti", "If you have any questions please let us know.": "Se hai una domanda faccelo sapere.", "Regards": "Cordialmente", @@ -93,7 +93,7 @@ "Getting started!": "Come iniziare!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Lingua predefinita", "The fallback Language, if something goes wrong": "La lingua secondaria, se qualcosa va storto", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Il lang-code dei datatables. <br><strong>Esempio:<\/strong> en-gb, fr_fr, de_de<br>Piu informazioni: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Il lang-code dei datatables. <br><strong>Esempio:</strong> en-gb, fr_fr, de_de<br>Piu informazioni: ", "Auto-translate": "Traduzione-automatica", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Se questo è abilitato la dashboard si traducerà automaticamente alla lingua dei clienti, se disponibile", "Client Language-Switch": "Switch delle lingue dei clienti", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Addebita la prima ora in crediti alla creazione di un server.", "Credits Display Name": "Nome di Visualizzazione dei Crediti", "PHPMyAdmin URL": "URL PHPMyAdmin", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Inserisci l'URL alla tua installazione di PHPMyAdmin. <strong>Senza lo slash finale!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Inserisci l'URL alla tua installazione di PHPMyAdmin. <strong>Senza lo slash finale!</strong>", "Pterodactyl URL": "URL di Pterodactyl", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Inserisci l'URL alla tua installazione di Pterodactyl. <strong>Senza un trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Inserisci l'URL alla tua installazione di Pterodactyl. <strong>Senza un trailing slash!</strong>", "Pterodactyl API Key": "Chiave API di Pterodactyl", "Enter the API Key to your Pterodactyl installation.": "Inserisci la Chiave API alla tua installazione di Pterodactyl.", "Force Discord verification": "Forza la verifica di Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Un voucher può essere utilizzato solo una volta per utente. Il numero di usi specifica il numero di utenti diversi che possono utilizzare questo voucher.", "Max": "Massimo", "Expires at": "Scade il", - "Used \/ Uses": "Usato \/ Utilizzi", + "Used / Uses": "Usato / Utilizzi", "Expires": "Scade", "Sign in to start your session": "Accedi per iniziare la sessione", "Password": "Password", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Nessun nodo è stato connesso!", "No nests available!": "Nessun nido (nest) disponibile!", "No eggs have been linked!": "Nessun uovo (egg) è stato connesso!", - "Software \/ Games": "Software \/ Giochi", + "Software / Games": "Software / Giochi", "Please select software ...": "Per favore selezione il software...", "---": "---", "Specification ": "Specifiche ", @@ -447,6 +447,8 @@ "Notes": "Note", "Amount in words": "Numero in parole", "Please pay until": "Per favore paga fino", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "L’aggiornamento / riduzione del tuo server reimposterà il tuo ciclo di fatturazione a ora. I tuoi crediti in eccesso saranno rimborsati. Il prezzo per il nuovo ciclo di fatturazione sarà prelevato", + "Caution": "Attenzione", "cs": "Ceco", "de": "Tedesco", "en": "Inglese", diff --git a/lang/nl.json b/lang/nl.json index 5e72fc395..6ff9ecf53 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Fout bij het maken van de server", "Your servers have been suspended!": "Uw servers zijn opgeschort!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Om uw server(s) automatisch opnieuw in te schakelen, moet u meer credits kopen.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Om uw server(s) automatisch opnieuw in te schakelen, moet u meer credits kopen.", "Purchase credits": "Credits kopen", "If you have any questions please let us know.": "Als u vragen heeft, laat het ons dan weten.", "Regards": "Met vriendelijke groet", @@ -93,7 +93,7 @@ "Getting started!": "Aan de slag!", "Welcome to our dashboard": "Welkom bij ons dashboard", "Verification": "Verificatie", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Informatie", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Standaard taal", "The fallback Language, if something goes wrong": "De terugval-taal, als er iets misgaat", "Datable language": "Dateerbare taal", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "De datatabellen lang-code. <br><strong>Voorbeeld:<\/strong> nl-gb, fr_fr, de_de<br>Meer informatie: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "De datatabellen lang-code. <br><strong>Voorbeeld:</strong> nl-gb, fr_fr, de_de<br>Meer informatie: ", "Auto-translate": "Automatisch vertalen", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Als dit is aangevinkt, zal het dashboard zichzelf vertalen naar de taal van de klant, indien beschikbaar", "Client Language-Switch": "Klant Taal-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Brengt het eerste uur aan credits in rekening bij het maken van een server.", "Credits Display Name": "Weergavenaam tegoed", "PHPMyAdmin URL": "PHPMyAdmin-URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Voer de URL naar uw PHPMyAdmin-installatie in. <strong>Zonder een slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Voer de URL naar uw PHPMyAdmin-installatie in. <strong>Zonder een slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Voer de URL naar uw Pterodactyl-installatie in. <strong>Zonder een slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Voer de URL naar uw Pterodactyl-installatie in. <strong>Zonder een slash!</strong>", "Pterodactyl API Key": "Pterodactyl API sleutel", "Enter the API Key to your Pterodactyl installation.": "Voer de API-sleutel in voor uw Pterodactyl-installatie.", "Force Discord verification": "Forceer Discord Verificatie", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Een voucher kan slechts één keer per gebruiker worden gebruikt. Gebruik geeft het aantal verschillende gebruikers aan dat deze voucher kan gebruiken.", "Max": "Maximum", "Expires at": "Verloopt om", - "Used \/ Uses": "Gebruikt \/ Toepassingen", + "Used / Uses": "Gebruikt / Toepassingen", "Expires": "Verloopt", "Sign in to start your session": "Login om te beginnen", "Password": "Wachtwoord", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Er zijn geen nodes gekoppeld!", "No nests available!": "Er zijn geen nesten beschikbaar!", "No eggs have been linked!": "Geen eieren gekoppeld!", - "Software \/ Games": "Software \/ Spellen", + "Software / Games": "Software / Spellen", "Please select software ...": "Selecteer software...", "---": "---", "Specification ": "Specificatie ", @@ -447,6 +447,8 @@ "Notes": "Notities", "Amount in words": "Hoeveelheid in eenheden", "Please pay until": "Gelieve te betalen tot", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgraden/downgraden van uw server zal uw facturatiecyclus resetten naar nu. Uw overbetalen krediet zal worden terugbetaald. De prijs voor de nieuwe facturatiecyclus zal worden afgeschreven", + "Caution": "Let op", "cs": "Tsjechisch", "de": "Duits", "en": "Engels", diff --git a/lang/pl.json b/lang/pl.json index 14bfaee38..b1f4164dd 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Błąd podczas tworzenia serwera", "Your servers have been suspended!": "Serwery zostały zawieszone!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Aby reaktywować swoje serwery, musisz kupić więcej kredytów.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Aby reaktywować swoje serwery, musisz kupić więcej kredytów.", "Purchase credits": "Zakup kredyty", "If you have any questions please let us know.": "W razie jakichkolwiek pytań prosimy o kontakt.", "Regards": "Z poważaniem", @@ -93,7 +93,7 @@ "Getting started!": "Zaczynajmy!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Domyślny język", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Domyślny język", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Automatyczne tłumaczenie", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Nazwa Waluty", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "URL Pterodactyl Panelu", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Klucz API Pterodactyl panelu", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Kupon może zostać zrealizowany tylko raz przez użytkownika. „Użycie” określa liczbę użytkowników, którzy mogą zrealizować ten kupon.", "Max": "Max", "Expires at": "Wygasa", - "Used \/ Uses": "Użyto \/ Użyć", + "Used / Uses": "Użyto / Użyć", "Expires": "Wygasa", "Sign in to start your session": "Zaloguj się, aby rozpocząć sesję", "Password": "Hasło", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Żaden węzeł nie został połączony!", "No nests available!": "Brak dostępnych gniazd!", "No eggs have been linked!": "Jajka nie zostały połaczone!", - "Software \/ Games": "Oprogramowanie \/ gry", + "Software / Games": "Oprogramowanie / gry", "Please select software ...": "Proszę wybrać oprogramowanie...", "---": "---", "Specification ": "Specyfikacja ", @@ -447,6 +447,8 @@ "Notes": "Uwagi", "Amount in words": "Wszystkie słowa", "Please pay until": "Zapłać w ciągu:", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizacja / degradacja twojego serwera spowoduje zresetowanie cyklu rozliczeniowego do teraz. Twoje nadpłacone kredyty zostaną zwrócone. Cena za nowy cykl rozliczeniowy zostanie pobrana", + "Caution": "Uwaga", "cs": "Czeski", "de": "Niemiecki", "en": "Angielski", diff --git a/lang/pt.json b/lang/pt.json index 1ce81866a..d31d5b9b0 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Alguém se registrou usando seu código!", "Server Creation Error": "Erro de criação do servidor", "Your servers have been suspended!": "Os seus servidores foram suspensos!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Para reativar automaticamente o seu(s) servidor(es), é preciso comprar mais créditos.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Para reativar automaticamente o seu(s) servidor(es), é preciso comprar mais créditos.", "Purchase credits": "Compra de créditos", "If you have any questions please let us know.": "Se tiver alguma dúvida, por favor, nos avise.", "Regards": "Cumprimentos,", @@ -93,7 +93,7 @@ "Getting started!": "Começar", "Welcome to our dashboard": "Bem-vindo ao nosso painel", "Verification": "Verificação", - "You can verify your e-mail address and link\/verify your Discord account.": "Você pode verificar o seu endereço de e-mail e link", + "You can verify your e-mail address and link/verify your Discord account.": "Você pode verificar o seu endereço de e-mail e link", "Information": "Informações", "This dashboard can be used to create and delete servers": "Este painel pode ser usado para criar e excluir servidores", "These servers can be used and managed on our pterodactyl panel": "Esses servidores podem ser usados e gerenciados no nosso painel pterodactyl ", @@ -187,7 +187,7 @@ "Default language": "Idioma padrão", "The fallback Language, if something goes wrong": "Um idioma padrão, se algo der errado", "Datable language": "Idioma de dados", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Os lang-codes disponíveis. <br><strong>Exemplo:<\/strong> en-gb, fr_fr, de_de<br>Mais informações:", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Os lang-codes disponíveis. <br><strong>Exemplo:</strong> en-gb, fr_fr, de_de<br>Mais informações:", "Auto-translate": "Traduzir Automaticamente", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Se isto for ativado, o Painel se traduzirá para o idioma do cliente, se disponível.", "Client Language-Switch": "Trocar Idioma do cliente", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Cobra a primeira hora de créditos ao criar um servidor.", "Credits Display Name": "Nome de exibição dos créditos", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Insira o URL do seu PHPMyAdmin. <strong>Sem barra de arrasto!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Insira o URL do seu PHPMyAdmin. <strong>Sem barra de arrasto!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Insira o URL do seu pterodactyl. <strong>Sem barra de arrasto!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Insira o URL do seu pterodactyl. <strong>Sem barra de arrasto!</strong>", "Pterodactyl API Key": "Chave API pterodactyl", "Enter the API Key to your Pterodactyl installation.": "Insira a chave API do seu painel pterodactyl.", "Force Discord verification": "Forçar verificação do Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Um vale só pode ser usado uma vez por utilizador. Os usos especificam o número de diferentes utilizadores que podem usar este comprovante.", "Max": "Máximo", "Expires at": "Expira em", - "Used \/ Uses": "Usados \/ Usos", + "Used / Uses": "Usados / Usos", "Expires": "Expira", "Sign in to start your session": "Entre para iniciar a sua sessão", "Password": "Senha", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Nenhum nó foi ligado!", "No nests available!": "Não há ninhos disponíveis!", "No eggs have been linked!": "Nenhum ovo foi ligado!", - "Software \/ Games": "“Software” \/ Jogos", + "Software / Games": "“Software” / Jogos", "Please select software ...": "Por favor, selecione o “software”...", "---": "—", "Specification ": "Especificação", @@ -447,6 +447,8 @@ "Notes": "Notas", "Amount in words": "Quantia em palavras", "Please pay until": "Favor pagar até", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Atualizar / Reduzir o seu servidor irá redefinir o seu ciclo de faturação para agora. Os seus créditos pagos a mais serão reembolsados. O preço para o novo ciclo de faturação será debitado", + "Caution": "Cuidado", "cs": "Tcheco", "de": "Alemão", "en": "Inglês", diff --git a/lang/ro.json b/lang/ro.json index f4c9004c1..ea3bdea16 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -93,7 +93,7 @@ "Getting started!": "Getting started!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -388,7 +388,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -447,6 +447,8 @@ "Notes": "Notes", "Amount in words": "Amount in words", "Please pay until": "Please pay until", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", "cs": "Czech", "de": "German", "en": "English", diff --git a/lang/ru.json b/lang/ru.json index 68755fabd..50e458e55 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -39,7 +39,7 @@ "Store item has been removed!": "Товар в магазине был удален!", "link has been created!": "Ссылка была создана!", "link has been updated!": "Ссылка была обновлена!", - "product has been removed!": "Продукт\/Товар был удалён!", + "product has been removed!": "Продукт/Товар был удалён!", "User does not exists on pterodactyl's panel": "Пользователь не был найден в панеле птеродактиль", "user has been removed!": "Пользователь был удален!", "Notification sent!": "Оповещение отправлено!", @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Ошибка создание сервера", "Your servers have been suspended!": "Ваши сервера были заблокированы!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Чтобы автоматически повторно включить ваш сервер \/ серверы, вам необходимо приобрести больше кредитов.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Чтобы автоматически повторно включить ваш сервер / серверы, вам необходимо приобрести больше кредитов.", "Purchase credits": "Приобрести кредиты", "If you have any questions please let us know.": "Пожалуйста, сообщите нам, если у Вас есть какие-либо вопросы.", "Regards": "С уважением,", @@ -93,7 +93,7 @@ "Getting started!": "Начало работы!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Язык по умолчанию", "The fallback Language, if something goes wrong": "Резервный язык, если что-то пойдет не так", "Datable language": "Датадатируемый язык", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Языковой код таблицы данных. <br><strong>Пример:<\/strong> en-gb, fr_fr, de_de<br>Дополнительная информация:", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Языковой код таблицы данных. <br><strong>Пример:</strong> en-gb, fr_fr, de_de<br>Дополнительная информация:", "Auto-translate": "Автоперевод", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Если этот флажок установлен, информационная панель будет переводиться на язык клиентов, если он доступен", "Client Language-Switch": "Переключение языка клиента", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Взимает кредиты за первый час при создании сервера.", "Credits Display Name": "Отображаемое имя кредитов", "PHPMyAdmin URL": "URL-адрес PHPMyAdmin", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Введите URL-адрес вашей установки PHPMyAdmin. <strong>Без косой черты в конце!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Введите URL-адрес вашей установки PHPMyAdmin. <strong>Без косой черты в конце!</strong>", "Pterodactyl URL": "URL-адрес птеродактиля", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Введите URL-адрес вашей установки Pterodactyl. <strong>Без косой черты в конце!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Введите URL-адрес вашей установки Pterodactyl. <strong>Без косой черты в конце!</strong>", "Pterodactyl API Key": "API-ключ птеродактиля", "Enter the API Key to your Pterodactyl installation.": "Введите ключ API для установки Pterodactyl.", "Force Discord verification": "Требуется верификация в Discord", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Промокод можно использовать только один раз для каждого пользователя. Пользователь указывает количество различных пользователей, которые могут использовать этот промокод.", "Max": "Макс.", "Expires at": "Срок действия до", - "Used \/ Uses": "Используется \/ Использует", + "Used / Uses": "Используется / Использует", "Expires": "Истекает", "Sign in to start your session": "Войдите, чтобы начать сессию", "Password": "Пароль", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Ни один узел не был связан!", "No nests available!": "Гнезда в наличии нет!", "No eggs have been linked!": "Группы были связаны!", - "Software \/ Games": "Программное обеспечение \/ Игры", + "Software / Games": "Программное обеспечение / Игры", "Please select software ...": "Пожалуйста, выберите программное обеспечение...", "---": "---", "Specification ": "Характеристики ", @@ -447,6 +447,8 @@ "Notes": "Примечания", "Amount in words": "Сумма прописью", "Please pay until": "Пожалуйста, платите до", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Обновление/Уменьшение вашего сервера сбросит ваш цикл оплаты на текущий. Ваши переплаты будут возвращены. Цена за новый цикл оплаты будет списана", + "Caution": "Внимание", "cs": "Czech", "de": "German", "en": "English", diff --git a/lang/sh.json b/lang/sh.json index cd3b0e50f..bdf0019ba 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -80,7 +80,7 @@ "User ID": "User ID", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -160,10 +160,10 @@ "please create a file called \"install.lock\" in your dashboard Root directory. Otherwise no settings will be loaded!": "please create a file called \"install.lock\" in your dashboard Root directory. Otherwise no settings will be loaded!", "or click here": "or click here", "Company Name": "Company Name", - "Company Address": "Company Address", + "Company Adress": "Company Adress", "Company Phonenumber": "Company Phonenumber", "VAT ID": "VAT ID", - "Company E-Mail Address": "Company E-Mail Address", + "Company E-Mail Adress": "Company E-Mail Adress", "Company Website": "Company Website", "Invoice Prefix": "Invoice Prefix", "Enable Invoices": "Enable Invoices", @@ -173,7 +173,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -185,7 +185,7 @@ "Mail Username": "Mail Username", "Mail Password": "Mail Password", "Mail Encryption": "Mail Encryption", - "Mail From Address": "Mail From Address", + "Mail From Adress": "Mail From Adress", "Mail From Name": "Mail From Name", "Discord Client-ID": "Discord Client-ID", "Discord Client-Secret": "Discord Client-Secret", @@ -214,9 +214,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -284,7 +284,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -354,7 +354,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -430,6 +430,8 @@ "The Language of the Datatables. Grab the Language-Codes from here": "The Language of the Datatables. Grab the Language-Codes from here", "Let the Client change the Language": "Let the Client change the Language", "Icons updated!": "Icons updated!", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", "cs": "Czech", "de": "German", "en": "English", @@ -441,28 +443,5 @@ "pl": "Polish", "zh": "Chinese", "tr": "Turkish", - "ru": "Russian", - "hourly": "Hourly", - "monthly": "Monthly", - "yearly": "Yearly", - "daily": "Daily", - "weekly": "Weekly", - "quarterly": "Quarterly", - "half-annually": "Half-annually", - "annually": "Annually", - "Suspended": "Suspended", - "Cancelled": "Cancelled", - "An exception has occurred while trying to cancel the server": "An exception has occurred while trying to cancel the server", - "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.": "This will cancel your current server to the next billing period. It will get suspended when the current period runs out.", - "Cancel Server?": "Cancel Server?", - "Delete Server?": "Delete Server?", - "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.": "This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.", - "Billing Period": "Billing Period", - "Next Billing Cycle": "Next Billing Cycle", - "Manage Server": "Manage Server", - "Delete Server": "Delete Server", - "Cancel Server": "Cancel Server", - "Yes, cancel it!": "Yes, cancel it!", - "No, abort!": "No, abort!", - "Billing period": "Billing period" + "ru": "Russian" } diff --git a/lang/sk.json b/lang/sk.json index 136d498de..dc88baf2c 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Server Creation Error", "Your servers have been suspended!": "Your servers have been suspended!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "To automatically re-enable your server\/s, you need to purchase more credits.", + "To automatically re-enable your server/s, you need to purchase more credits.": "To automatically re-enable your server/s, you need to purchase more credits.", "Purchase credits": "Purchase credits", "If you have any questions please let us know.": "If you have any questions please let us know.", "Regards": "Regards", @@ -93,7 +93,7 @@ "Getting started!": "Getting started!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Default language", "The fallback Language, if something goes wrong": "The fallback Language, if something goes wrong", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Auto-translate", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -388,7 +388,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -447,6 +447,8 @@ "Notes": "Notes", "Amount in words": "Amount in words", "Please pay until": "Please pay until", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizácia alebo deaktualizácia servera resetuje Vašu fakturačnú dobu na aktuálny čas. Vaše nadbytočné kredity budú vrátené. Cena za novú fakturačnú dobu bude odobraná.", + "Caution": "Upozornenie", "cs": "Czech", "de": "German", "en": "English", diff --git a/lang/sr.json b/lang/sr.json index 6e23c5260..599516313 100644 --- a/lang/sr.json +++ b/lang/sr.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Greška pri kreiranju servera", "Your servers have been suspended!": "Vaši serveri su suspendovani!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Da biste automatski ponovo omogućili svoje servere, potrebno je da kupite još kredita.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Da biste automatski ponovo omogućili svoje servere, potrebno je da kupite još kredita.", "Purchase credits": "Kupite kredite", "If you have any questions please let us know.": "Ako imate bilo kakvih pitanja, molimo vas da nas obavestite.", "Regards": "Pozdravi", @@ -93,7 +93,7 @@ "Getting started!": "Početak!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Podrazumevani jezik", "The fallback Language, if something goes wrong": "Sekundarni jezik, u slučaju da bude problema", "Datable language": "Datable language", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ", "Auto-translate": "Automatski prevod", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Korisnički izbor za jezik", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Naplaćuje kredite u vrednosti od prvog sata prilikom kreiranja servera.", "Credits Display Name": "Ime prikaza kredita", "PHPMyAdmin URL": "PHPMyAdmin Link", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Unesite URL adresu instalacije PHPMyAdmin. <strong>Bez kose crte!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Unesite URL adresu instalacije PHPMyAdmin. <strong>Bez kose crte!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Maksimalno", "Expires at": "Ističe", - "Used \/ Uses": "Upotrebljeno \/ Upotrebe", + "Used / Uses": "Upotrebljeno / Upotrebe", "Expires": "Ističe", "Sign in to start your session": "Prijavite se da biste započeli sesiju", "Password": "Lozinka", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Node-ovi nisu povezani!", "No nests available!": "Nema dostupnih gnezda!", "No eggs have been linked!": "Jaja nisu povezana!", - "Software \/ Games": "Softver \/ Igrice", + "Software / Games": "Softver / Igrice", "Please select software ...": "Molimo izaberite softver ...", "---": "---", "Specification ": "Specification ", @@ -447,6 +447,8 @@ "Notes": "Napomena", "Amount in words": "Iznos u rečima", "Please pay until": "Molimo platite do", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", "cs": "Czech", "de": "German", "en": "English", diff --git a/lang/sv.json b/lang/sv.json index 805887ef8..41d5d1c72 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Someone registered using your Code!", "Server Creation Error": "Serverskapande fel", "Your servers have been suspended!": "Ditt konto har blivit avstängt!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "För att automatiskt återaktivera din server\/s, så måste du köpa mer krediter.", + "To automatically re-enable your server/s, you need to purchase more credits.": "För att automatiskt återaktivera din server/s, så måste du köpa mer krediter.", "Purchase credits": "Köp krediter", "If you have any questions please let us know.": "Kontakta oss gärna om du har några eventuella frågor.", "Regards": "Hälsningar", @@ -93,7 +93,7 @@ "Getting started!": "Kom igång!", "Welcome to our dashboard": "Welcome to our dashboard", "Verification": "Verification", - "You can verify your e-mail address and link\/verify your Discord account.": "You can verify your e-mail address and link\/verify your Discord account.", + "You can verify your e-mail address and link/verify your Discord account.": "You can verify your e-mail address and link/verify your Discord account.", "Information": "Information", "This dashboard can be used to create and delete servers": "This dashboard can be used to create and delete servers", "These servers can be used and managed on our pterodactyl panel": "These servers can be used and managed on our pterodactyl panel", @@ -187,7 +187,7 @@ "Default language": "Förvalt språk", "The fallback Language, if something goes wrong": "Reservspråket, om något går fel", "Datable language": "Daterbart språk", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Datatabellernas språkkod. <br><strong>Exempel:<\/strong> en-gb, fr_fr, de_de<br>Mer information: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Datatabellernas språkkod. <br><strong>Exempel:</strong> en-gb, fr_fr, de_de<br>Mer information: ", "Auto-translate": "Auto-översätt", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "If this is checked, the Dashboard will translate itself to the Clients language, if available", "Client Language-Switch": "Client Language-Switch", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Charges the first hour worth of credits upon creating a server.", "Credits Display Name": "Credits Display Name", "PHPMyAdmin URL": "PHPMyAdmin URL", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>", "Pterodactyl URL": "Pterodactyl URL", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>", "Pterodactyl API Key": "Pterodactyl API Key", "Enter the API Key to your Pterodactyl installation.": "Enter the API Key to your Pterodactyl installation.", "Force Discord verification": "Force Discord verification", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.", "Max": "Max", "Expires at": "Expires at", - "Used \/ Uses": "Used \/ Uses", + "Used / Uses": "Used / Uses", "Expires": "Expires", "Sign in to start your session": "Sign in to start your session", "Password": "Password", @@ -388,7 +388,7 @@ "No nodes have been linked!": "No nodes have been linked!", "No nests available!": "No nests available!", "No eggs have been linked!": "No eggs have been linked!", - "Software \/ Games": "Software \/ Games", + "Software / Games": "Software / Games", "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", @@ -447,6 +447,8 @@ "Notes": "Notes", "Amount in words": "Amount in words", "Please pay until": "Please pay until", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", "cs": "Czech", "de": "German", "en": "English", diff --git a/lang/tr.json b/lang/tr.json index f712fd680..7d34213fc 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "Birileri senin kodunu kullanarak kayıt oldu!", "Server Creation Error": "Sunucu Oluşturma Hatası", "Your servers have been suspended!": "Sunucularınız askıya alındı!", - "To automatically re-enable your server\/s, you need to purchase more credits.": "Sunucularınızı\/sunucularınızı otomatik olarak yeniden etkinleştirmek için daha fazla kredi satın almanız gerekir.", + "To automatically re-enable your server/s, you need to purchase more credits.": "Sunucularınızı/sunucularınızı otomatik olarak yeniden etkinleştirmek için daha fazla kredi satın almanız gerekir.", "Purchase credits": "Satın alma kredisi", "If you have any questions please let us know.": "Herhangi bir sorunuz varsa lütfen bize bildirin.", "Regards": "Saygılarımızla", @@ -93,7 +93,7 @@ "Getting started!": "Başlarken!", "Welcome to our dashboard": "Kontrol panelimize hoş geldiniz", "Verification": "Doğrulama", - "You can verify your e-mail address and link\/verify your Discord account.": "E-posta adresinizi doğrulayabilir ve Discord hesabınızı bağlayabilir\/doğrulayabilirsiniz.", + "You can verify your e-mail address and link/verify your Discord account.": "E-posta adresinizi doğrulayabilir ve Discord hesabınızı bağlayabilir/doğrulayabilirsiniz.", "Information": "Bilgi", "This dashboard can be used to create and delete servers": "Bu gösterge panosu, sunucular oluşturmak ve silmek için kullanılabilir", "These servers can be used and managed on our pterodactyl panel": "Bu sunucular pterodactyl panelimizde kullanılabilir ve yönetilebilir", @@ -187,7 +187,7 @@ "Default language": "Varsayılan Dil", "The fallback Language, if something goes wrong": "Yedek dil, eğer bir şeyler yanlış giderse", "Datable language": "Tarih Dili", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "Tarihlerde kullanılacak dil kodu. <br><strong>Örnek:<\/strong> en-gb, fr_fr, de_de<br> Daha fazla bilgi: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "Tarihlerde kullanılacak dil kodu. <br><strong>Örnek:</strong> en-gb, fr_fr, de_de<br> Daha fazla bilgi: ", "Auto-translate": "Otomatik çeviri", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "Eğer bu seçili ise Yönetim paneli kendisini kullanıcının diline çevirecek, eğer kullanılabiliyorsa", "Client Language-Switch": "Müşteri Dil Değiştiricisi", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "Kullanıcı sunucu oluşturduğumda ilk saatin ödemesini direkt olarak alır.", "Credits Display Name": "Kredi ismi", "PHPMyAdmin URL": "PHPMyAdmin linki", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "PHPMyAdmin kurulumunuzun linkini girin <strong> Sonda eğik çizgi olmadan<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "PHPMyAdmin kurulumunuzun linkini girin <strong> Sonda eğik çizgi olmadan</strong>", "Pterodactyl URL": "Pterodactyl Linki", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "Pterodactyl kurulumunuzun linkini girin <strong> Sonda eğik çizgi olmadan!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "Pterodactyl kurulumunuzun linkini girin <strong> Sonda eğik çizgi olmadan!</strong>", "Pterodactyl API Key": "Pterodactyl API Anahtarı", "Enter the API Key to your Pterodactyl installation.": "Pterodactyl kurulumunuzun API anahtarını girin.", "Force Discord verification": "Discord Doğrulamasını zorunlu yap", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "Bir kupon, kullanıcı başına yalnızca bir kez kullanılabilir. Kullanımlar, bu kuponu kullanabilecek farklı kullanıcıların sayısını belirtir.", "Max": "Maks", "Expires at": "Sona eriyor", - "Used \/ Uses": "Kullanılmış \/ Kullanım Alanları", + "Used / Uses": "Kullanılmış / Kullanım Alanları", "Expires": "Sona eriyor", "Sign in to start your session": "Oturumunuzu başlatmak için oturum açın", "Password": "Parola", @@ -388,7 +388,7 @@ "No nodes have been linked!": "Hiçbir makine bağlanmamış!", "No nests available!": "Hiçbir nest bulunamadı!", "No eggs have been linked!": "Hiçbir egg bağlanmamış!", - "Software \/ Games": "Yazılımlar \/ Oyunlar", + "Software / Games": "Yazılımlar / Oyunlar", "Please select software ...": "Lütfen bir yazılım seçin ...", "---": "---", "Specification ": "Özellikler ", @@ -447,6 +447,8 @@ "Notes": "Notlar", "Amount in words": "Yazı ile Tutar", "Please pay until": "Lütfen şu tarihe kadar ödeyin", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Sunucunuzu yükseltmek / düşürmek faturalandırma döngünüzü şimdiye sıfırlayacaktır. Aşırı ödenen kredileriniz iade edilecektir. Yeni faturalandırma döngüsü için ödenen tutar çekilecektir", + "Caution": "Dikkat", "cs": "Çekçe", "de": "Almanca", "en": "İngilizce", diff --git a/lang/zh.json b/lang/zh.json index 0e665123e..907ed1695 100644 --- a/lang/zh.json +++ b/lang/zh.json @@ -81,7 +81,7 @@ "Someone registered using your Code!": "已经有人使用您的代码注册了", "Server Creation Error": "服务器创建错误", "Your servers have been suspended!": "您的服务器已被暂停", - "To automatically re-enable your server\/s, you need to purchase more credits.": "如需重新启用你的服务器,您需要购买更多的余额", + "To automatically re-enable your server/s, you need to purchase more credits.": "如需重新启用你的服务器,您需要购买更多的余额", "Purchase credits": "购买余额", "If you have any questions please let us know.": "如果您有其他任何问题,欢迎联系我们。", "Regards": "此致", @@ -93,7 +93,7 @@ "Getting started!": "开始吧!", "Welcome to our dashboard": "欢迎访问 dashboard", "Verification": "验证", - "You can verify your e-mail address and link\/verify your Discord account.": "你可以验证你的邮箱地址或者连接到你的Discord账户", + "You can verify your e-mail address and link/verify your Discord account.": "你可以验证你的邮箱地址或者连接到你的Discord账户", "Information": "相关信息", "This dashboard can be used to create and delete servers": "此仪表板可用于创建和删除服务器", "These servers can be used and managed on our pterodactyl panel": "这些服务器可以在我们的pterodactyl面板上使用和管理", @@ -187,7 +187,7 @@ "Default language": "默认语言", "The fallback Language, if something goes wrong": "备用语言", "Datable language": "可用语言", - "The datatables lang-code. <br><strong>Example:<\/strong> en-gb, fr_fr, de_de<br>More Information: ": "数据表语言代码 <br><strong>示例:<\/strong> en-gb、fr_fr、de_de<br> 更多信息: ", + "The datatables lang-code. <br><strong>Example:</strong> en-gb, fr_fr, de_de<br>More Information: ": "数据表语言代码 <br><strong>示例:</strong> en-gb、fr_fr、de_de<br> 更多信息: ", "Auto-translate": "自动翻译", "If this is checked, the Dashboard will translate itself to the Clients language, if available": "如果勾选此项,系统将把自己翻译成客户语言(如果有)", "Client Language-Switch": "客户语言切换", @@ -243,9 +243,9 @@ "Charges the first hour worth of credits upon creating a server.": "在创建服务器时收取第一个小时的费用", "Credits Display Name": "积分显示名称", "PHPMyAdmin URL": "PHPMyAdmin地址", - "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!<\/strong>": "输入你PHPMyAdmin的URL。<strong>不要有尾部斜线!<\/strong>", + "Enter the URL to your PHPMyAdmin installation. <strong>Without a trailing slash!</strong>": "输入你PHPMyAdmin的URL。<strong>不要有尾部斜线!</strong>", "Pterodactyl URL": "Pterodactyl地址", - "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!<\/strong>": "输入你Pterodactyl的URL。<strong>不要有尾部斜线!<\/strong>", + "Enter the URL to your Pterodactyl installation. <strong>Without a trailing slash!</strong>": "输入你Pterodactyl的URL。<strong>不要有尾部斜线!</strong>", "Pterodactyl API Key": "Pterodactyl API密钥", "Enter the API Key to your Pterodactyl installation.": "输入Pterodactyl API密钥", "Force Discord verification": "强制Discord验证", @@ -316,7 +316,7 @@ "A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher.": "每个用户只能使用一次代金券。使用次数指定了可以使用此代金券的不同用户的数量。", "Max": "最大", "Expires at": "过期时间", - "Used \/ Uses": "已使用\/使用情况", + "Used / Uses": "已使用/使用情况", "Expires": "过期", "Sign in to start your session": "登录以开始您的会议", "Password": "密码", @@ -388,7 +388,7 @@ "No nodes have been linked!": "没有节点被链接!", "No nests available!": "没有可用的巢穴!", "No eggs have been linked!": "没有蛋被链接!", - "Software \/ Games": "软件\/游戏", + "Software / Games": "软件/游戏", "Please select software ...": "请选择软件...", "---": "---", "Specification ": "规格 ", @@ -447,6 +447,8 @@ "Notes": "笔记", "Amount in words": "税额的字数", "Please pay until": "请支付至", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "升级/降级你的服务器将重置你的账单周期。你的多余的点数将被退还。新的账单周期的价格将被扣除", + "Caution": "警告", "cs": "捷克语", "de": "德语", "en": "英语", From 27d2a48e10e9e9a4e54677cea6db1b4a7cda1f63 Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Mon, 19 Dec 2022 15:08:09 +0100 Subject: [PATCH 092/514] chore: doc --- Addon-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Addon-notes.md b/Addon-notes.md index bed6f5a2e..ac00939be 100644 --- a/Addon-notes.md +++ b/Addon-notes.md @@ -1,4 +1,4 @@ Export diff files: Commit Hash of lates Main commit -git diff -r --no-commit-id --name-only --diff-filter=ACMR \<commit> | tar -czf ../controllpanelgg-monthly-addon/file.tgz -T - +git diff -r --no-commit-id --name-only --diff-filter=ACMR \<commit> | tar -czf \.\./controllpanelgg-monthly-addon/file.tgz -T - From fda40fe8bcd34d762017036beea15971c57af6eb Mon Sep 17 00:00:00 2001 From: IceToast <> Date: Mon, 23 Jan 2023 16:27:32 +0100 Subject: [PATCH 093/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Merge=20missing?= =?UTF-8?q?=20hidden=20product=20in=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/servers/create.blade.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 2a0a3d841..0a91cc046 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -229,7 +229,8 @@ class="custom-select"> </div> <div class="mt-auto border rounded border-secondary"> <div class="d-flex justify-content-between p-2"> - <span class="d-inline-block mr-4" x-text="'{{ __('Price') }}' + ' (' + product.billing_period + ')'"> + <span class="d-inline-block mr-4" + x-text="'{{ __('Price') }}' + ' (' + product.billing_period + ')'"> </span> <span class="d-inline-block" x-text="product.price + ' {{ CREDITS_DISPLAY_NAME }}'"></span> @@ -250,13 +251,12 @@ class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" </div> </div> </div> + </template> </div> - </template> </div> - </div> - </form> - <!-- END FORM --> + </form> + <!-- END FORM --> </div> </section> From 255671e20da007c7f6a5fed7d2d53b16062a8b79 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Mon, 6 Feb 2023 14:45:44 +0100 Subject: [PATCH 094/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Merge=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 50 ++- app/Models/User.php | 13 +- resources/views/servers/index.blade.php | 295 ---------------- themes/default/views/servers/create.blade.php | 22 +- themes/default/views/servers/index.blade.php | 324 +++++++++++++----- 5 files changed, 269 insertions(+), 435 deletions(-) delete mode 100644 resources/views/servers/index.blade.php diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 61ac4218a..8822ad3ab 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -33,7 +33,7 @@ public function index() //Get server infos from ptero $serverAttributes = Pterodactyl::getServerAttributes($server->pterodactyl_id, true); - if (! $serverAttributes) { + if (!$serverAttributes) { continue; } $serverRelationships = $serverAttributes['relationships']; @@ -70,7 +70,7 @@ public function index() /** Show the form for creating a new resource. */ public function create() { - if (! is_null($this->validateConfigurationRules())) { + if (!is_null($this->validateConfigurationRules())) { return $this->validateConfigurationRules(); } @@ -125,7 +125,7 @@ private function validateConfigurationRules() // Check if node has enough memory and disk space $checkResponse = Pterodactyl::checkNodeResources($node, $product->memory, $product->disk); if ($checkResponse == false) { - return redirect()->route('servers.index')->with('error', __("The node '".$nodeName."' doesn't have the required memory or disk left to allocate this product.")); + return redirect()->route('servers.index')->with('error', __("The node '" . $nodeName . "' doesn't have the required memory or disk left to allocate this product.")); } // Min. Credits @@ -133,23 +133,23 @@ private function validateConfigurationRules() Auth::user()->credits < $product->minimum_credits || Auth::user()->credits < $product->price ) { - return redirect()->route('servers.index')->with('error', 'You do not have the required amount of '.CREDITS_DISPLAY_NAME.' to use this product!'); + return redirect()->route('servers.index')->with('error', 'You do not have the required amount of ' . CREDITS_DISPLAY_NAME . ' to use this product!'); } } //Required Verification for creating an server - if (config('SETTINGS::USER:FORCE_EMAIL_VERIFICATION', 'false') === 'true' && ! Auth::user()->hasVerifiedEmail()) { + if (config('SETTINGS::USER:FORCE_EMAIL_VERIFICATION', 'false') === 'true' && !Auth::user()->hasVerifiedEmail()) { return redirect()->route('profile.index')->with('error', __('You are required to verify your email address before you can create a server.')); } //Required Verification for creating an server - if (! config('SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS', 'true') && Auth::user()->role != 'admin') { + if (!config('SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS', 'true') && Auth::user()->role != 'admin') { return redirect()->route('servers.index')->with('error', __('The system administrator has blocked the creation of new servers.')); } //Required Verification for creating an server - if (config('SETTINGS::USER:FORCE_DISCORD_VERIFICATION', 'false') === 'true' && ! Auth::user()->discordUser) { + if (config('SETTINGS::USER:FORCE_DISCORD_VERIFICATION', 'false') === 'true' && !Auth::user()->discordUser) { return redirect()->route('profile.index')->with('error', __('You are required to link your discord account before you can create a server.')); } @@ -162,7 +162,7 @@ public function store(Request $request) /** @var Node $node */ /** @var Egg $egg */ /** @var Product $product */ - if (! is_null($this->validateConfigurationRules())) { + if (!is_null($this->validateConfigurationRules())) { return $this->validateConfigurationRules(); } @@ -186,7 +186,7 @@ public function store(Request $request) //get free allocation ID $allocationId = Pterodactyl::getFreeAllocationId($node); - if (! $allocationId) { + if (!$allocationId) { return $this->noAllocationsError($server); } @@ -249,13 +249,9 @@ public function destroy(Server $server) } /** Cancel Server */ - public function cancel (Server $server) + public function cancel(Server $server) { try { - error_log($server->update([ - 'cancelled' => now(), - ])); - return redirect()->route('servers.index')->with('success', __('Server cancelled')); } catch (Exception $e) { return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to cancel the server"') . $e->getMessage() . '"'); @@ -267,7 +263,9 @@ public function show(Server $server) { - if($server->user_id != Auth::user()->id){ return back()->with('error', __('This is not your Server!'));} + if ($server->user_id != Auth::user()->id) { + return back()->with('error', __('This is not your Server!')); + } $serverAttributes = Pterodactyl::getServerAttributes($server->pterodactyl_id); $serverRelationships = $serverAttributes['relationships']; $serverLocationAttributes = $serverRelationships['location']['attributes']; @@ -287,10 +285,10 @@ public function show(Server $server) $pteroNode = Pterodactyl::getNode($serverRelationships['node']['attributes']['id']); $products = Product::orderBy('created_at') - ->whereHas('nodes', function (Builder $builder) use ($serverRelationships) { //Only show products for that node - $builder->where('id', '=', $serverRelationships['node']['attributes']['id']); - }) - ->get(); + ->whereHas('nodes', function (Builder $builder) use ($serverRelationships) { //Only show products for that node + $builder->where('id', '=', $serverRelationships['node']['attributes']['id']); + }) + ->get(); // Set the each product eggs array to just contain the eggs name foreach ($products as $product) { @@ -308,9 +306,8 @@ public function show(Server $server) public function upgrade(Server $server, Request $request) { - if($server->user_id != Auth::user()->id || $server->suspended) return redirect()->route('servers.index'); - if(!isset($request->product_upgrade)) - { + if ($server->user_id != Auth::user()->id || $server->suspended) return redirect()->route('servers.index'); + if (!isset($request->product_upgrade)) { return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('this product is the only one')); } $user = Auth::user(); @@ -329,7 +326,7 @@ public function upgrade(Server $server, Request $request) $requiredisk = $newProduct->disk - $oldProduct->disk; $checkResponse = Pterodactyl::checkNodeResources($node, $requireMemory, $requiredisk); if ($checkResponse == false) { - return redirect()->route('servers.index')->with('error', __("The node '".$nodeName."' doesn't have the required memory or disk left to upgrade the server.")); + return redirect()->route('servers.index')->with('error', __("The node '" . $nodeName . "' doesn't have the required memory or disk left to upgrade the server.")); } // calculate the amount of credits that the user overpayed for the old product when canceling the server right now @@ -353,8 +350,7 @@ public function upgrade(Server $server, Request $request) $overpayedCredits = $oldProduct->price - $oldProduct->price * ($timeDifference / $billingPeriodMultiplier); - if ($user->credits >= $newProduct->price && $user->credits >= $newProduct->minimum_credits) - { + if ($user->credits >= $newProduct->price && $user->credits >= $newProduct->minimum_credits) { $server->allocation = $serverAttributes['allocation']; // Update the server on the panel $response = Pterodactyl::updateServer($server, $newProduct); @@ -374,11 +370,11 @@ public function upgrade(Server $server, Request $request) if ($overpayedCredits > 0) $user->increment('credits', $overpayedCredits); // Withdraw the credits for the new product - $user->decrement('credits', $newProduct->price); + $user->decrement('credits', $newProduct->price); //restart the server $response = Pterodactyl::powerAction($server, "restart"); - if ($response->failed()) return redirect()->route('servers.index')->with('error', 'Server upgraded successfully! Could not restart the server: '.$response->json()['errors'][0]['detail']); + if ($response->failed()) return redirect()->route('servers.index')->with('error', 'Server upgraded successfully! Could not restart the server: ' . $response->json()['errors'][0]['detail']); return redirect()->route('servers.show', ['server' => $server->id])->with('success', __('Server Successfully Upgraded')); } else { return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('Not Enough Balance for Upgrade')); diff --git a/app/Models/User.php b/app/Models/User.php index 91e15cab3..199bb25d2 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -185,7 +185,7 @@ public function isSuspended() } /** - * @throws Exception + * @throws \Exception */ public function suspend() { @@ -201,7 +201,7 @@ public function suspend() } /** - * @throws Exception + * @throws \Exception */ public function unSuspend() { @@ -218,12 +218,6 @@ public function unSuspend() return $this; } - private function getServersWithProduct() - { - return $this->servers() - ->with('product') - ->get(); - } /** * @return string @@ -257,7 +251,8 @@ public function creditUsage() return number_format($usage, 2, '.', ''); } - private function getServersWithProduct() { + private function getServersWithProduct() + { return $this->servers() ->whereNull('suspended') ->whereNull('cancelled') diff --git a/resources/views/servers/index.blade.php b/resources/views/servers/index.blade.php deleted file mode 100644 index 7910a30af..000000000 --- a/resources/views/servers/index.blade.php +++ /dev/null @@ -1,295 +0,0 @@ -@extends('layouts.main') - -@section('content') - <!-- CONTENT HEADER --> - <section class="content-header"> - <div class="container-fluid"> - <div class="row mb-2"> - <div class="col-sm-6"> - <h1>{{ __('Servers') }}</h1> - </div> - <div class="col-sm-6"> - <ol class="breadcrumb float-sm-right"> - <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li> - <li class="breadcrumb-item"><a class="text-muted" - href="{{ route('servers.index') }}">{{ __('Servers') }}</a> - </li> - </ol> - </div> - </div> - </div> - </section> - <!-- END CONTENT HEADER --> - - <!-- MAIN CONTENT --> - <section class="content"> - <div class="container-fluid"> - - <!-- CUSTOM CONTENT --> - <div class="d-flex justify-content-md-start justify-content-center mb-3 "> - <a @if (Auth::user()->Servers->count() >= Auth::user()->server_limit) - disabled="disabled" title="Server limit reached!" - @endif href="{{ route('servers.create') }}" - class="btn - @if (Auth::user()->Servers->count() >= Auth::user()->server_limit) disabled - @endif btn-primary"><i - class="fa fa-plus mr-2"></i> - {{ __('Create Server') }} - </a> - </div> - - <div class="row d-flex flex-row justify-content-center justify-content-md-start"> - @foreach ($servers as $server) - <div class="col-xl-3 col-lg-5 col-md-6 col-sm-6 col-xs-12 card pr-0 pl-0 ml-sm-2 mr-sm-3" - style="max-width: 350px"> - <div class="card-header"> - <div class="d-flex justify-content-between align-items-center"> - <h5 class="card-title mt-1">{{ $server->name }} - </h5> - <div class="card-tools mt-1"> - <div class="dropdown no-arrow"> - <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" - aria-haspopup="true" aria-expanded="false"> - <i class="fas fa-ellipsis-v fa-sm fa-fw text-white-50"></i> - </a> - <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" - aria-labelledby="dropdownMenuLink"> - @if (!empty(config('SETTINGS::MISC:PHPMYADMIN:URL'))) - <a href="{{ config('SETTINGS::MISC:PHPMYADMIN:URL') }}" - class="dropdown-item text-info" target="__blank"><i title="manage" - class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span></a> - @endif - <div class="dropdown-divider"></div> - <span class="dropdown-item"><i title="Created at" - class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') }}</span></span> - </div> - </div> - </div> - </div> - </div> - <div class="card-body"> - <div class="container mt-1"> - <div class="row mb-3"> - <div class="col my-auto">{{ __('Status') }}:</div> - <div class="col-7 my-auto"> - @if($server->suspended) - <span class="badge badge-danger">{{ __('Suspended') }}</span> - @elseif($server->cancelled) - <span class="badge badge-warning">{{ __('Cancelled') }}</span> - @else - <span class="badge badge-success">{{ __('Active') }}</span> - @endif - </div> - </div> - <div class="row mb-2"> - <div class="col-5"> - {{ __('Location') }}: - </div> - <div class="col-7 d-flex justify-content-between align-items-center"> - <span class="">{{ $server->location }}</span> - <i data-toggle="popover" data-trigger="hover" - data-content="{{ __('Node') }}: {{ $server->node }}" - class="fas fa-info-circle"></i> - </div> - - </div> - <div class="row mb-2"> - <div class="col-5 "> - {{ __('Software') }}: - </div> - <div class="col-7 text-wrap"> - <span>{{ $server->nest }}</span> - </div> - - </div> - <div class="row mb-2"> - <div class="col-5 "> - {{ __('Specification') }}: - </div> - <div class="col-7 text-wrap"> - <span>{{ $server->egg }}</span> - </div> - </div> - <div class="row mb-2"> - <div class="col-5 "> - {{ __('Resource plan') }}: - </div> - <div class="col-7 text-wrap d-flex justify-content-between align-items-center"> - <span>{{ $server->product->name }} - </span> - <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/> {{ __('Billing Period') }}: {{$server->product->billing_period}}" - class="fas fa-info-circle"></i> - </div> - </div> - - <div class="row mb-4 "> - <div class="col-5 word-break" style="hyphens: auto"> - {{ __('Next Billing Cycle') }}: - </div> - <div class="col-7 d-flex text-wrap align-items-center"> - <span> - @if ($server->suspended) - - - @else - @switch($server->product->billing_period) - @case('monthly') - {{ \Carbon\Carbon::parse($server->last_billed)->addMonth()->toDayDateTimeString(); }} - @break - @case('weekly') - {{ \Carbon\Carbon::parse($server->last_billed)->addWeek()->toDayDateTimeString(); }} - @break - @case('daily') - {{ \Carbon\Carbon::parse($server->last_billed)->addDay()->toDayDateTimeString(); }} - @break - @case('hourly') - {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} - @break - @case('quarterly') - {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(3)->toDayDateTimeString(); }} - @break - @case('half-annually') - {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} - @break - @case('annually') - {{ \Carbon\Carbon::parse($server->last_billed)->addYear()->toDayDateTimeString(); }} - @break - @default - {{ __('Unknown') }} - @endswitch - @endif - </span> - </div> - </div> - - <div class="row mb-2"> - <div class="col-4"> - {{ __('Price') }}: - <span class="text-muted"> - ({{ CREDITS_DISPLAY_NAME }}) - </span> - </div> - <div class="col-8 text-center"> - <div class="text-muted"> - @if($server->product->billing_period == 'monthly') - {{ __('per Month') }} - @elseif($server->product->billing_period == 'half-annually') - {{ __('per 6 Months') }} - @elseif($server->product->billing_period == 'quarterly') - {{ __('per 3 Months') }} - @elseif($server->product->billing_period == 'annually') - {{ __('per Year') }} - @elseif($server->product->billing_period == 'weekly') - {{ __('per Week') }} - @elseif($server->product->billing_period == 'daily') - {{ __('per Day') }} - @elseif($server->product->billing_period == 'hourly') - {{ __('per Hour') }} - @endif - </div> - <span> - {{ $server->product->price == round($server->product->price) ? round($server->product->price) : $server->product->price }} - </span> - </div> - </div> - </div> - </div> - - <div class="card-footer text-center"> - <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" - target="__blank" - class="btn btn-info text-center float-left ml-2" - data-toggle="tooltip" data-placement="bottom" title="{{ __('Manage Server') }}"> - <i class="fas fa-tools mx-2"></i> - </a> - @if(config("SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN")) - <a href="{{ route('servers.show', ['server' => $server->id])}}" - class="btn btn-info text-center mr-3" - data-toggle="tooltip" data-placement="bottom" title="{{ __('Server Settings') }}"> - <i class="fas fa-cog mx-2"></i> - </a> - @endif - <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" - class="btn btn-warning text-center" - {{ $server->suspended || $server->cancelled ? "disabled" : "" }} - data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> - <i class="fas fa-ban mx-2"></i> - </button> - <button onclick="handleServerDelete('{{ $server->id }}');" target="__blank" - class="btn btn-danger text-center float-right mr-2" - data-toggle="tooltip" data-placement="bottom" title="{{ __('Delete Server') }}"> - <i class="fas fa-trash mx-2"></i> - </button> - </div> - </div> - @endforeach - </div> - <!-- END CUSTOM CONTENT --> - </div> - </section> - <!-- END CONTENT --> - - <script> - const handleServerCancel = (serverId) => { - // Handle server cancel with sweetalert - Swal.fire({ - title: "{{ __('Cancel Server?') }}", - text: "{{ __('This will cancel your current server to the next billing period. It will get suspended when the current period runs out.') }}", - icon: 'warning', - confirmButtonColor: '#d9534f', - showCancelButton: true, - confirmButtonText: "{{ __('Yes, cancel it!') }}", - cancelButtonText: "{{ __('No, abort!') }}", - reverseButtons: true - }).then((result) => { - if (result.value) { - // Delete server - fetch("{{ route('servers.cancel', '') }}" + '/' + serverId, { - method: 'PATCH', - headers: { - 'X-CSRF-TOKEN': '{{ csrf_token() }}' - } - }).then(() => { - window.location.reload(); - }); - return - } - }) - } - - const handleServerDelete = (serverId) => { - Swal.fire({ - title: "{{ __('Delete Server?') }}", - html: "{{!! __('This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.') !!}}", - icon: 'warning', - confirmButtonColor: '#d9534f', - showCancelButton: true, - confirmButtonText: "{{ __('Yes, delete it!') }}", - cancelButtonText: "{{ __('No, abort!') }}", - reverseButtons: true - }).then((result) => { - if (result.value) { - // Delete server - fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { - method: 'DELETE', - headers: { - 'X-CSRF-TOKEN': '{{ csrf_token() }}' - } - }).then(() => { - window.location.reload(); - }); - return - } - }); - - } - - document.addEventListener('DOMContentLoaded', () => { - $('[data-toggle="popover"]').popover(); - }); - - $(function () { - $('[data-toggle="tooltip"]').tooltip() - }) - </script> -@endsection diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 0a91cc046..ec18ebfe4 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -213,11 +213,11 @@ class="custom-select"> <span class="d-inline-block" x-text="product.billing_period"></span> </li> - <li> + <li class="d-flex justify-content-between"> <span class="d-inline-block"><i class="fa fa-coins"></i> {{ __('Minimum') }} {{ CREDITS_DISPLAY_NAME }}</span> <span class="d-inline-block" - x-text="product.minimum_credits == -1 ? {{ config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER') }} : product.minimum_credits"></span> + x-text="product.minimum_credits == -1 ? {{ config('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER') }} : Math.round(product.minimum_credits)"></span> </li> </ul> </div> @@ -240,14 +240,16 @@ class="custom-select"> <input type="hidden" name="product" x-model="selectedProduct"> </div> <div> - <button type="submit" x-model="selectedProduct" name="product" - :disabled="product.minimum_credits > user.credits || product.price > user.credits || product.doesNotFit == true || - submitClicked" - :class="product.minimum_credits > user.credits || product.price > user.credits || product.doesNotFit == true || - submitClicked ? 'disabled' : ''" - class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" - x-text="product.doesNotFit == true ? '{{ __('Server cant fit on this Node') }}' : (product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : '{{ __('Create server') }}')"> - </button> + <button type="submit" x-model="selectedProduct" name="product" + :disabled="product.minimum_credits > user.credits || product.price > user.credits || + product.doesNotFit == true || + submitClicked" + :class="product.minimum_credits > user.credits || product.price > user.credits || + product.doesNotFit == true || + submitClicked ? 'disabled' : ''" + class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" + x-text="product.doesNotFit == true ? '{{ __('Server cant fit on this Node') }}' : (product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ CREDITS_DISPLAY_NAME }}!' : '{{ __('Create server') }}')"> + </button> </div> </div> </div> diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index ef856b6a0..7910a30af 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -36,124 +36,260 @@ class="btn class="fa fa-plus mr-2"></i> {{ __('Create Server') }} </a> - @if (Auth::user()->Servers->count() > 0&&!empty(config('SETTINGS::MISC:PHPMYADMIN:URL'))) - <a - href="{{ config('SETTINGS::MISC:PHPMYADMIN:URL') }}" target="_blank" - class="btn btn-secondary ml-2"><i title="manage" - class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span> - </a> - @endif </div> <div class="row d-flex flex-row justify-content-center justify-content-md-start"> @foreach ($servers as $server) - @if($server->location&&$server->node&&$server->nest&&$server->egg) - <div class="col-xl-3 col-lg-5 col-md-6 col-sm-6 col-xs-12 card pr-0 pl-0 ml-sm-2 mr-sm-3" - style="max-width: 350px"> - <div class="card-header"> - <div class="d-flex justify-content-between align-items-center"> - <h5 class="card-title mt-1">{{ $server->name }} - </h5> + <div class="col-xl-3 col-lg-5 col-md-6 col-sm-6 col-xs-12 card pr-0 pl-0 ml-sm-2 mr-sm-3" + style="max-width: 350px"> + <div class="card-header"> + <div class="d-flex justify-content-between align-items-center"> + <h5 class="card-title mt-1">{{ $server->name }} + </h5> + <div class="card-tools mt-1"> + <div class="dropdown no-arrow"> + <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" + aria-haspopup="true" aria-expanded="false"> + <i class="fas fa-ellipsis-v fa-sm fa-fw text-white-50"></i> + </a> + <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" + aria-labelledby="dropdownMenuLink"> + @if (!empty(config('SETTINGS::MISC:PHPMYADMIN:URL'))) + <a href="{{ config('SETTINGS::MISC:PHPMYADMIN:URL') }}" + class="dropdown-item text-info" target="__blank"><i title="manage" + class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span></a> + @endif + <div class="dropdown-divider"></div> + <span class="dropdown-item"><i title="Created at" + class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') }}</span></span> + </div> + </div> </div> </div> - <div class="card-body"> - <div class="container mt-1"> - <div class="row mb-3"> - <div class="col my-auto">{{ __('Status') }}:</div> - <div class="col-7 my-auto"> - <i - class="fas {{ $server->isSuspended() ? 'text-danger' : 'text-success' }} fa-circle mr-2"></i> - {{ $server->isSuspended() ? 'Suspended' : 'Active' }} - </div> + </div> + <div class="card-body"> + <div class="container mt-1"> + <div class="row mb-3"> + <div class="col my-auto">{{ __('Status') }}:</div> + <div class="col-7 my-auto"> + @if($server->suspended) + <span class="badge badge-danger">{{ __('Suspended') }}</span> + @elseif($server->cancelled) + <span class="badge badge-warning">{{ __('Cancelled') }}</span> + @else + <span class="badge badge-success">{{ __('Active') }}</span> + @endif + </div> + </div> + <div class="row mb-2"> + <div class="col-5"> + {{ __('Location') }}: + </div> + <div class="col-7 d-flex justify-content-between align-items-center"> + <span class="">{{ $server->location }}</span> + <i data-toggle="popover" data-trigger="hover" + data-content="{{ __('Node') }}: {{ $server->node }}" + class="fas fa-info-circle"></i> </div> - <div class="row mb-2"> - <div class="col-5"> - {{ __('Location') }}: - </div> - <div class="col-7 d-flex justify-content-between align-items-center"> - <span class="">{{ $server->location }}</span> - <i data-toggle="popover" data-trigger="hover" - data-content="{{ __('Node') }}: {{ $server->node }}" - class="fas fa-info-circle"></i> - </div> + </div> + <div class="row mb-2"> + <div class="col-5 "> + {{ __('Software') }}: + </div> + <div class="col-7 text-wrap"> + <span>{{ $server->nest }}</span> </div> - <div class="row mb-2"> - <div class="col-5 "> - {{ __('Software') }}: - </div> - <div class="col-7 text-wrap"> - <span>{{ $server->nest }}</span> - </div> + </div> + <div class="row mb-2"> + <div class="col-5 "> + {{ __('Specification') }}: </div> - <div class="row mb-2"> - <div class="col-5 "> - {{ __('Specification') }}: - </div> - <div class="col-7 text-wrap"> - <span>{{ $server->egg }}</span> - </div> + <div class="col-7 text-wrap"> + <span>{{ $server->egg }}</span> </div> - <div class="row mb-4"> - <div class="col-5 "> - {{ __('Resource plan') }}: - </div> - <div class="col-7 text-wrap d-flex justify-content-between align-items-center"> - <span>{{ $server->product->name }} - </span> - <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/>" - class="fas fa-info-circle"></i> - </div> + </div> + <div class="row mb-2"> + <div class="col-5 "> + {{ __('Resource plan') }}: + </div> + <div class="col-7 text-wrap d-flex justify-content-between align-items-center"> + <span>{{ $server->product->name }} + </span> + <i data-toggle="popover" data-trigger="hover" data-html="true" + data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/> {{ __('Billing Period') }}: {{$server->product->billing_period}}" + class="fas fa-info-circle"></i> + </div> + </div> + <div class="row mb-4 "> + <div class="col-5 word-break" style="hyphens: auto"> + {{ __('Next Billing Cycle') }}: </div> - <div class="row mb-2"> - <div class="col-4"> - {{ __('Price') }}: - <span class="text-muted"> - ({{ CREDITS_DISPLAY_NAME }}) - </span> - </div> - <div class="col-8"> - <div class="row"> - <div class="col-6 text-center"> - <div class="text-muted">{{ __('per Hour') }}</div> - <span> - {{ number_format($server->product->getHourlyPrice(), 2, '.', '') }} - </span> - </div> - <div class="col-6 text-center"> - <div class="text-muted">{{ __('per Month') }} - </div> - <span> - {{ $server->product->getHourlyPrice() * 24 * 30 }} - </span> - </div> + <div class="col-7 d-flex text-wrap align-items-center"> + <span> + @if ($server->suspended) + - + @else + @switch($server->product->billing_period) + @case('monthly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonth()->toDayDateTimeString(); }} + @break + @case('weekly') + {{ \Carbon\Carbon::parse($server->last_billed)->addWeek()->toDayDateTimeString(); }} + @break + @case('daily') + {{ \Carbon\Carbon::parse($server->last_billed)->addDay()->toDayDateTimeString(); }} + @break + @case('hourly') + {{ \Carbon\Carbon::parse($server->last_billed)->addHour()->toDayDateTimeString(); }} + @break + @case('quarterly') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(3)->toDayDateTimeString(); }} + @break + @case('half-annually') + {{ \Carbon\Carbon::parse($server->last_billed)->addMonths(6)->toDayDateTimeString(); }} + @break + @case('annually') + {{ \Carbon\Carbon::parse($server->last_billed)->addYear()->toDayDateTimeString(); }} + @break + @default + {{ __('Unknown') }} + @endswitch + @endif + </span> + </div> + </div> + + <div class="row mb-2"> + <div class="col-4"> + {{ __('Price') }}: + <span class="text-muted"> + ({{ CREDITS_DISPLAY_NAME }}) + </span> + </div> + <div class="col-8 text-center"> + <div class="text-muted"> + @if($server->product->billing_period == 'monthly') + {{ __('per Month') }} + @elseif($server->product->billing_period == 'half-annually') + {{ __('per 6 Months') }} + @elseif($server->product->billing_period == 'quarterly') + {{ __('per 3 Months') }} + @elseif($server->product->billing_period == 'annually') + {{ __('per Year') }} + @elseif($server->product->billing_period == 'weekly') + {{ __('per Week') }} + @elseif($server->product->billing_period == 'daily') + {{ __('per Day') }} + @elseif($server->product->billing_period == 'hourly') + {{ __('per Hour') }} + @endif </div> - </div> + <span> + {{ $server->product->price == round($server->product->price) ? round($server->product->price) : $server->product->price }} + </span> </div> </div> </div> + </div> - <div class="card-footer d-flex align-items-center justify-content-between"> - <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" - target="__blank" - class="btn btn-info mx-3 w-100 align-items-center justify-content-center d-flex"> - <i class="fas fa-tools mr-2"></i> - <span>{{ __('Manage') }}</span> - </a> - <a href="{{ route('servers.show', ['server' => $server->id])}}" class="btn btn-warning mx-3 w-100 align-items-center justify-content-center d-flex"> - <i class="fas fa-cog mr-2"></i> - <span>{{ __('Settings') }}</span> - </a> - </div> + <div class="card-footer text-center"> + <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" + target="__blank" + class="btn btn-info text-center float-left ml-2" + data-toggle="tooltip" data-placement="bottom" title="{{ __('Manage Server') }}"> + <i class="fas fa-tools mx-2"></i> + </a> + @if(config("SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN")) + <a href="{{ route('servers.show', ['server' => $server->id])}}" + class="btn btn-info text-center mr-3" + data-toggle="tooltip" data-placement="bottom" title="{{ __('Server Settings') }}"> + <i class="fas fa-cog mx-2"></i> + </a> + @endif + <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" + class="btn btn-warning text-center" + {{ $server->suspended || $server->cancelled ? "disabled" : "" }} + data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> + <i class="fas fa-ban mx-2"></i> + </button> + <button onclick="handleServerDelete('{{ $server->id }}');" target="__blank" + class="btn btn-danger text-center float-right mr-2" + data-toggle="tooltip" data-placement="bottom" title="{{ __('Delete Server') }}"> + <i class="fas fa-trash mx-2"></i> + </button> </div> - @endif + </div> @endforeach </div> <!-- END CUSTOM CONTENT --> </div> </section> <!-- END CONTENT --> + + <script> + const handleServerCancel = (serverId) => { + // Handle server cancel with sweetalert + Swal.fire({ + title: "{{ __('Cancel Server?') }}", + text: "{{ __('This will cancel your current server to the next billing period. It will get suspended when the current period runs out.') }}", + icon: 'warning', + confirmButtonColor: '#d9534f', + showCancelButton: true, + confirmButtonText: "{{ __('Yes, cancel it!') }}", + cancelButtonText: "{{ __('No, abort!') }}", + reverseButtons: true + }).then((result) => { + if (result.value) { + // Delete server + fetch("{{ route('servers.cancel', '') }}" + '/' + serverId, { + method: 'PATCH', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + window.location.reload(); + }); + return + } + }) + } + + const handleServerDelete = (serverId) => { + Swal.fire({ + title: "{{ __('Delete Server?') }}", + html: "{{!! __('This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.') !!}}", + icon: 'warning', + confirmButtonColor: '#d9534f', + showCancelButton: true, + confirmButtonText: "{{ __('Yes, delete it!') }}", + cancelButtonText: "{{ __('No, abort!') }}", + reverseButtons: true + }).then((result) => { + if (result.value) { + // Delete server + fetch("{{ route('servers.destroy', '') }}" + '/' + serverId, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + window.location.reload(); + }); + return + } + }); + + } + + document.addEventListener('DOMContentLoaded', () => { + $('[data-toggle="popover"]').popover(); + }); + + $(function () { + $('[data-toggle="tooltip"]').tooltip() + }) + </script> @endsection From 971226ee8e088c37c0780d9fe4b0c22fb9bdc83c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sat, 29 Apr 2023 23:30:35 +0200 Subject: [PATCH 095/514] update composer dependencies --- composer.lock | 528 +++++++++++++++++++++++++------------------------- 1 file changed, 263 insertions(+), 265 deletions(-) diff --git a/composer.lock b/composer.lock index b46d1632d..96825a92c 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.262.3", + "version": "3.269.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "f5c8142d43846194bbb3bb40b18e7f6df2788409" + "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f5c8142d43846194bbb3bb40b18e7f6df2788409", - "reference": "f5c8142d43846194bbb3bb40b18e7f6df2788409", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", + "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", "shasum": "" }, "require": { @@ -81,7 +81,7 @@ "ext-simplexml": "*", "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", "guzzlehttp/promises": "^1.4.0", - "guzzlehttp/psr7": "^1.8.5 || ^2.3", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "mtdowling/jmespath.php": "^2.6", "php": ">=5.5" }, @@ -100,6 +100,7 @@ "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", "psr/cache": "^1.0", + "psr/http-message": "^1.0", "psr/simple-cache": "^1.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" @@ -150,9 +151,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.262.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.269.0" }, - "time": "2023-03-28T18:18:50+00:00" + "time": "2023-04-26T18:21:04+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -527,16 +528,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.1", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "57815c7bbcda3cd18871d253c1dd8cbe56f8526e" + "reference": "b4bd1cfbd2b916951696d82e57d054394d84864c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/57815c7bbcda3cd18871d253c1dd8cbe56f8526e", - "reference": "57815c7bbcda3cd18871d253c1dd8cbe56f8526e", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/b4bd1cfbd2b916951696d82e57d054394d84864c", + "reference": "b4bd1cfbd2b916951696d82e57d054394d84864c", "shasum": "" }, "require": { @@ -552,9 +553,9 @@ "doctrine/coding-standard": "11.1.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.10.3", + "phpstan/phpstan": "1.10.9", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.4", + "phpunit/phpunit": "9.6.6", "psalm/plugin-phpunit": "0.18.4", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", @@ -619,7 +620,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.1" + "source": "https://github.com/doctrine/dbal/tree/3.6.2" }, "funding": [ { @@ -635,7 +636,7 @@ "type": "tidelift" } ], - "time": "2023-03-02T19:26:24+00:00" + "time": "2023-04-14T07:25:38+00:00" }, { "name": "doctrine/deprecations", @@ -1317,22 +1318,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9", + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1425,7 +1426,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/7.5.1" }, "funding": [ { @@ -1441,7 +1442,7 @@ "type": "tidelift" } ], - "time": "2022-08-28T15:39:27+00:00" + "time": "2023-04-17T16:30:08+00:00" }, { "name": "guzzlehttp/promises", @@ -1529,22 +1530,22 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.4", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf" + "reference": "b635f279edd83fc275f822a1188157ffea568ff6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", - "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "ralouphie/getallheaders": "^3.0" }, "provide": { @@ -1564,9 +1565,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "2.4-dev" } }, "autoload": { @@ -1628,7 +1626,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.4" + "source": "https://github.com/guzzle/psr7/tree/2.5.0" }, "funding": [ { @@ -1644,7 +1642,7 @@ "type": "tidelift" } ], - "time": "2023-03-09T13:19:02+00:00" + "time": "2023-04-17T16:11:26+00:00" }, { "name": "guzzlehttp/uri-template", @@ -1732,16 +1730,16 @@ }, { "name": "hidehalo/nanoid-php", - "version": "1.1.12", + "version": "1.1.13", "source": { "type": "git", "url": "https://github.com/hidehalo/nanoid-php.git", - "reference": "3229400d7e69b127a9e4f8fdad2e498e64cdaae4" + "reference": "3fc7c949f4e655939cc30e7110d658af3dbb0e30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hidehalo/nanoid-php/zipball/3229400d7e69b127a9e4f8fdad2e498e64cdaae4", - "reference": "3229400d7e69b127a9e4f8fdad2e498e64cdaae4", + "url": "https://api.github.com/repos/hidehalo/nanoid-php/zipball/3fc7c949f4e655939cc30e7110d658af3dbb0e30", + "reference": "3fc7c949f4e655939cc30e7110d658af3dbb0e30", "shasum": "" }, "require": { @@ -1783,9 +1781,9 @@ ], "support": { "issues": "https://github.com/hidehalo/nanoid-php/issues", - "source": "https://github.com/hidehalo/nanoid-php/tree/1.1.12" + "source": "https://github.com/hidehalo/nanoid-php/tree/1.1.13" }, - "time": "2021-12-30T07:27:43+00:00" + "time": "2022-08-04T12:07:12+00:00" }, { "name": "kkomelin/laravel-translatable-string-exporter", @@ -1854,16 +1852,16 @@ }, { "name": "laravel/framework", - "version": "v9.52.5", + "version": "v9.52.7", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "e14d28c0f9403630d13f308bb43f3d3cb73d6d67" + "reference": "675ea868fe36b18c8303e954aac540e6b1caa677" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/e14d28c0f9403630d13f308bb43f3d3cb73d6d67", - "reference": "e14d28c0f9403630d13f308bb43f3d3cb73d6d67", + "url": "https://api.github.com/repos/laravel/framework/zipball/675ea868fe36b18c8303e954aac540e6b1caa677", + "reference": "675ea868fe36b18c8303e954aac540e6b1caa677", "shasum": "" }, "require": { @@ -1960,7 +1958,7 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^7.16", + "orchestra/testbench-core": "^7.24", "pda/pheanstalk": "^4.0", "phpstan/phpdoc-parser": "^1.15", "phpstan/phpstan": "^1.4.7", @@ -2048,7 +2046,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-03-28T18:03:54+00:00" + "time": "2023-04-25T13:44:05+00:00" }, { "name": "laravel/serializable-closure", @@ -2565,16 +2563,16 @@ }, { "name": "league/flysystem", - "version": "3.12.3", + "version": "3.14.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "81e87e74dd5213795c7846d65089712d2dda90ce" + "reference": "e2a279d7f47d9098e479e8b21f7fb8b8de230158" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/81e87e74dd5213795c7846d65089712d2dda90ce", - "reference": "81e87e74dd5213795c7846d65089712d2dda90ce", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e2a279d7f47d9098e479e8b21f7fb8b8de230158", + "reference": "e2a279d7f47d9098e479e8b21f7fb8b8de230158", "shasum": "" }, "require": { @@ -2636,7 +2634,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.12.3" + "source": "https://github.com/thephpleague/flysystem/tree/3.14.0" }, "funding": [ { @@ -2646,26 +2644,22 @@ { "url": "https://github.com/frankdejonge", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" } ], - "time": "2023-02-18T15:32:41+00:00" + "time": "2023-04-11T18:11:47+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.12.2", + "version": "3.13.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "645e14e4a80bd2da8b01e57388e7296a695a80c2" + "reference": "8e04cbb403d4dfd5b73a2f8685f1df395bd177eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/645e14e4a80bd2da8b01e57388e7296a695a80c2", - "reference": "645e14e4a80bd2da8b01e57388e7296a695a80c2", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/8e04cbb403d4dfd5b73a2f8685f1df395bd177eb", + "reference": "8e04cbb403d4dfd5b73a2f8685f1df395bd177eb", "shasum": "" }, "require": { @@ -2706,7 +2700,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.12.2" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.13.0" }, "funding": [ { @@ -2716,13 +2710,9 @@ { "url": "https://github.com/frankdejonge", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" } ], - "time": "2023-01-17T14:15:08+00:00" + "time": "2023-03-16T14:29:01+00:00" }, { "name": "league/mime-type-detection", @@ -2858,26 +2848,24 @@ }, { "name": "masterminds/html5", - "version": "2.7.6", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "897eb517a343a2281f11bc5556d6548db7d93947" + "reference": "3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/897eb517a343a2281f11bc5556d6548db7d93947", - "reference": "897eb517a343a2281f11bc5556d6548db7d93947", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3", + "reference": "3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-dom": "*", - "ext-libxml": "*", "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8" }, "type": "library", "extra": { @@ -2921,9 +2909,9 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.7.6" + "source": "https://github.com/Masterminds/html5-php/tree/2.8.0" }, - "time": "2022-08-18T16:18:26+00:00" + "time": "2023-04-26T07:27:39+00:00" }, { "name": "monolog/monolog", @@ -3959,16 +3947,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.16.1", + "version": "1.20.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571" + "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/e27e92d939e2e3636f0a1f0afaba59692c0bf571", - "reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6c04009f6cae6eda2f040745b6b846080ef069c2", + "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2", "shasum": "" }, "require": { @@ -3998,9 +3986,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.16.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.3" }, - "time": "2023-02-07T18:11:17+00:00" + "time": "2023-04-25T09:01:03+00:00" }, { "name": "predis/predis", @@ -4214,21 +4202,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -4248,7 +4236,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -4260,27 +4248,27 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client/tree/1.0.2" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-04-10T20:12:12+00:00" }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -4300,7 +4288,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -4315,31 +4303,31 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -4354,7 +4342,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -4368,9 +4356,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/log", @@ -4475,16 +4463,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.14", + "version": "v0.11.16", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "8c2e264def7a8263a68ef6f0b55ce90b77d41e17" + "reference": "151b145906804eea8e5d71fea23bfb470c904bfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/8c2e264def7a8263a68ef6f0b55ce90b77d41e17", - "reference": "8c2e264def7a8263a68ef6f0b55ce90b77d41e17", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/151b145906804eea8e5d71fea23bfb470c904bfb", + "reference": "151b145906804eea8e5d71fea23bfb470c904bfb", "shasum": "" }, "require": { @@ -4545,9 +4533,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.14" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.16" }, - "time": "2023-03-28T03:41:01+00:00" + "time": "2023-04-26T12:53:57+00:00" }, { "name": "qirolab/laravel-themer", @@ -4754,16 +4742,16 @@ }, { "name": "ramsey/uuid", - "version": "4.x-dev", + "version": "4.7.4", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "8e955307d32dc9b6992440ff81321d3cb09db75a" + "reference": "60a4c63ab724854332900504274f6150ff26d286" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8e955307d32dc9b6992440ff81321d3cb09db75a", - "reference": "8e955307d32dc9b6992440ff81321d3cb09db75a", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286", + "reference": "60a4c63ab724854332900504274f6150ff26d286", "shasum": "" }, "require": { @@ -4804,7 +4792,6 @@ "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, - "default-branch": true, "type": "library", "extra": { "captainhook": { @@ -4831,7 +4818,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.x" + "source": "https://github.com/ramsey/uuid/tree/4.7.4" }, "funding": [ { @@ -4843,7 +4830,7 @@ "type": "tidelift" } ], - "time": "2023-03-27T22:05:11+00:00" + "time": "2023-04-15T23:01:58+00:00" }, { "name": "sabberworm/php-css-parser", @@ -5115,16 +5102,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.14.2", + "version": "1.15.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "bab62023a4745a61170ad5424184533685e73c2d" + "reference": "efab1844b8826443135201c4443690f032c3d533" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/bab62023a4745a61170ad5424184533685e73c2d", - "reference": "bab62023a4745a61170ad5424184533685e73c2d", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/efab1844b8826443135201c4443690f032c3d533", + "reference": "efab1844b8826443135201c4443690f032c3d533", "shasum": "" }, "require": { @@ -5163,7 +5150,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.14.2" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.15.0" }, "funding": [ { @@ -5171,7 +5158,7 @@ "type": "github" } ], - "time": "2023-03-14T16:41:21+00:00" + "time": "2023-04-27T08:09:01+00:00" }, { "name": "spatie/laravel-query-builder", @@ -5247,20 +5234,19 @@ }, { "name": "spatie/laravel-settings", - "version": "2.8.2", + "version": "2.8.3", "source": { "type": "git", "url": "https://github.com/spatie/laravel-settings.git", - "reference": "48140731312d73ad000921e23d53b8e1fe5247cf" + "reference": "9193603a3e02d19af9f2fd0d309ac2469b25d680" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-settings/zipball/48140731312d73ad000921e23d53b8e1fe5247cf", - "reference": "48140731312d73ad000921e23d53b8e1fe5247cf", + "url": "https://api.github.com/repos/spatie/laravel-settings/zipball/9193603a3e02d19af9f2fd0d309ac2469b25d680", + "reference": "9193603a3e02d19af9f2fd0d309ac2469b25d680", "shasum": "" }, "require": { - "doctrine/dbal": "^2.13|^3.2", "ext-json": "*", "illuminate/database": "^8.73|^9.0|^10.0", "php": "^7.4|^8.0", @@ -5319,7 +5305,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-settings/issues", - "source": "https://github.com/spatie/laravel-settings/tree/2.8.2" + "source": "https://github.com/spatie/laravel-settings/tree/2.8.3" }, "funding": [ { @@ -5331,7 +5317,7 @@ "type": "github" } ], - "time": "2023-03-10T09:30:34+00:00" + "time": "2023-03-30T12:47:39+00:00" }, { "name": "spatie/laravel-validation-rules", @@ -5408,16 +5394,16 @@ }, { "name": "spatie/temporary-directory", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/spatie/temporary-directory.git", - "reference": "e2818d871783d520b319c2d38dc37c10ecdcde20" + "reference": "0c804873f6b4042aa8836839dca683c7d0f71831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/e2818d871783d520b319c2d38dc37c10ecdcde20", - "reference": "e2818d871783d520b319c2d38dc37c10ecdcde20", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/0c804873f6b4042aa8836839dca683c7d0f71831", + "reference": "0c804873f6b4042aa8836839dca683c7d0f71831", "shasum": "" }, "require": { @@ -5453,7 +5439,7 @@ ], "support": { "issues": "https://github.com/spatie/temporary-directory/issues", - "source": "https://github.com/spatie/temporary-directory/tree/2.1.1" + "source": "https://github.com/spatie/temporary-directory/tree/2.1.2" }, "funding": [ { @@ -5465,7 +5451,7 @@ "type": "github" } ], - "time": "2022-08-23T07:15:15+00:00" + "time": "2023-04-28T07:47:42+00:00" }, { "name": "stripe/stripe-php", @@ -5529,16 +5515,16 @@ }, { "name": "symfony/console", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cbad09eb8925b6ad4fb721c7a179344dc4a19d45" + "reference": "12288d9f4500f84a4d02254d4aa968b15488476f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cbad09eb8925b6ad4fb721c7a179344dc4a19d45", - "reference": "cbad09eb8925b6ad4fb721c7a179344dc4a19d45", + "url": "https://api.github.com/repos/symfony/console/zipball/12288d9f4500f84a4d02254d4aa968b15488476f", + "reference": "12288d9f4500f84a4d02254d4aa968b15488476f", "shasum": "" }, "require": { @@ -5600,12 +5586,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.7" + "source": "https://github.com/symfony/console/tree/v6.2.10" }, "funding": [ { @@ -5621,7 +5607,7 @@ "type": "tidelift" } ], - "time": "2023-02-25T17:00:03+00:00" + "time": "2023-04-28T13:37:43+00:00" }, { "name": "symfony/css-selector", @@ -5757,16 +5743,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "61e90f94eb014054000bc902257d2763fac09166" + "reference": "8b7e9f124640cb0611624a9383176c3e5f7d8cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/61e90f94eb014054000bc902257d2763fac09166", - "reference": "61e90f94eb014054000bc902257d2763fac09166", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b7e9f124640cb0611624a9383176c3e5f7d8cfb", + "reference": "8b7e9f124640cb0611624a9383176c3e5f7d8cfb", "shasum": "" }, "require": { @@ -5808,7 +5794,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.2.7" + "source": "https://github.com/symfony/error-handler/tree/v6.2.10" }, "funding": [ { @@ -5824,20 +5810,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:44:56+00:00" + "time": "2023-04-18T13:46:08+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.2.7", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "404b307de426c1c488e5afad64403e5f145e82a5" + "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/404b307de426c1c488e5afad64403e5f145e82a5", - "reference": "404b307de426c1c488e5afad64403e5f145e82a5", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/04046f35fd7d72f9646e721fc2ecb8f9c67d3339", + "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339", "shasum": "" }, "require": { @@ -5891,7 +5877,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.7" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.8" }, "funding": [ { @@ -5907,7 +5893,7 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:44:56+00:00" + "time": "2023-03-20T16:06:02+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6054,16 +6040,16 @@ }, { "name": "symfony/http-client", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "0a5be6cbc570ae23b51b49d67341f378629d78e4" + "reference": "3f5545a91c8e79dedd1a06c4b04e1682c80c42f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/0a5be6cbc570ae23b51b49d67341f378629d78e4", - "reference": "0a5be6cbc570ae23b51b49d67341f378629d78e4", + "url": "https://api.github.com/repos/symfony/http-client/zipball/3f5545a91c8e79dedd1a06c4b04e1682c80c42f9", + "reference": "3f5545a91c8e79dedd1a06c4b04e1682c80c42f9", "shasum": "" }, "require": { @@ -6118,8 +6104,11 @@ ], "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", "homepage": "https://symfony.com", + "keywords": [ + "http" + ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.2.7" + "source": "https://github.com/symfony/http-client/tree/v6.2.10" }, "funding": [ { @@ -6135,7 +6124,7 @@ "type": "tidelift" } ], - "time": "2023-02-21T10:54:55+00:00" + "time": "2023-04-20T13:12:48+00:00" }, { "name": "symfony/http-client-contracts", @@ -6220,16 +6209,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5fc3038d4a594223f9ea42e4e985548f3fcc9a3b" + "reference": "49adbb92bcb4e3c2943719d2756271e8b9602acc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5fc3038d4a594223f9ea42e4e985548f3fcc9a3b", - "reference": "5fc3038d4a594223f9ea42e4e985548f3fcc9a3b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49adbb92bcb4e3c2943719d2756271e8b9602acc", + "reference": "49adbb92bcb4e3c2943719d2756271e8b9602acc", "shasum": "" }, "require": { @@ -6278,7 +6267,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.2.7" + "source": "https://github.com/symfony/http-foundation/tree/v6.2.10" }, "funding": [ { @@ -6294,20 +6283,20 @@ "type": "tidelift" } ], - "time": "2023-02-21T10:54:55+00:00" + "time": "2023-04-18T13:46:08+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "ca0680ad1e2d678536cc20e0ae33f9e4e5d2becd" + "reference": "81064a65a5496f17d2b6984f6519406f98864215" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ca0680ad1e2d678536cc20e0ae33f9e4e5d2becd", - "reference": "ca0680ad1e2d678536cc20e0ae33f9e4e5d2becd", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/81064a65a5496f17d2b6984f6519406f98864215", + "reference": "81064a65a5496f17d2b6984f6519406f98864215", "shasum": "" }, "require": { @@ -6389,7 +6378,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.2.7" + "source": "https://github.com/symfony/http-kernel/tree/v6.2.10" }, "funding": [ { @@ -6405,20 +6394,20 @@ "type": "tidelift" } ], - "time": "2023-02-28T13:26:41+00:00" + "time": "2023-04-28T13:50:28+00:00" }, { "name": "symfony/intl", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "e7346ea6d88ae22e1b5d489b7a60135e72527cec" + "reference": "860c99e53149d22df1900d3aefdaeb17adb7669d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/e7346ea6d88ae22e1b5d489b7a60135e72527cec", - "reference": "e7346ea6d88ae22e1b5d489b7a60135e72527cec", + "url": "https://api.github.com/repos/symfony/intl/zipball/860c99e53149d22df1900d3aefdaeb17adb7669d", + "reference": "860c99e53149d22df1900d3aefdaeb17adb7669d", "shasum": "" }, "require": { @@ -6459,7 +6448,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a PHP replacement layer for the C intl extension that includes additional data from the ICU library", + "description": "Provides access to the localization data of the ICU library", "homepage": "https://symfony.com", "keywords": [ "i18n", @@ -6470,7 +6459,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v6.2.7" + "source": "https://github.com/symfony/intl/tree/v6.2.10" }, "funding": [ { @@ -6486,20 +6475,20 @@ "type": "tidelift" } ], - "time": "2023-02-21T10:54:55+00:00" + "time": "2023-04-14T16:23:31+00:00" }, { "name": "symfony/mailer", - "version": "v6.2.7", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "e4f84c633b72ec70efc50b8016871c3bc43e691e" + "reference": "bfcfa015c67e19c6fdb7ca6fe70700af1e740a17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/e4f84c633b72ec70efc50b8016871c3bc43e691e", - "reference": "e4f84c633b72ec70efc50b8016871c3bc43e691e", + "url": "https://api.github.com/repos/symfony/mailer/zipball/bfcfa015c67e19c6fdb7ca6fe70700af1e740a17", + "reference": "bfcfa015c67e19c6fdb7ca6fe70700af1e740a17", "shasum": "" }, "require": { @@ -6549,7 +6538,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.2.7" + "source": "https://github.com/symfony/mailer/tree/v6.2.8" }, "funding": [ { @@ -6565,20 +6554,20 @@ "type": "tidelift" } ], - "time": "2023-02-21T10:35:38+00:00" + "time": "2023-03-14T15:00:05+00:00" }, { "name": "symfony/mailgun-mailer", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "9e27b8ec2f6ee7575c6229a61be1578a5a4b21ee" + "reference": "2c9d47b11cc154d2db3f571030cd965d128de1a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/9e27b8ec2f6ee7575c6229a61be1578a5a4b21ee", - "reference": "9e27b8ec2f6ee7575c6229a61be1578a5a4b21ee", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/2c9d47b11cc154d2db3f571030cd965d128de1a8", + "reference": "2c9d47b11cc154d2db3f571030cd965d128de1a8", "shasum": "" }, "require": { @@ -6614,7 +6603,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v6.2.7" + "source": "https://github.com/symfony/mailgun-mailer/tree/v6.2.10" }, "funding": [ { @@ -6630,20 +6619,20 @@ "type": "tidelift" } ], - "time": "2023-02-21T10:35:38+00:00" + "time": "2023-04-14T16:23:31+00:00" }, { "name": "symfony/mime", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "62e341f80699badb0ad70b31149c8df89a2d778e" + "reference": "b6c137fc53a9f7c4c951cd3f362b3734c7a97723" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/62e341f80699badb0ad70b31149c8df89a2d778e", - "reference": "62e341f80699badb0ad70b31149c8df89a2d778e", + "url": "https://api.github.com/repos/symfony/mime/zipball/b6c137fc53a9f7c4c951cd3f362b3734c7a97723", + "reference": "b6c137fc53a9f7c4c951cd3f362b3734c7a97723", "shasum": "" }, "require": { @@ -6697,7 +6686,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.2.7" + "source": "https://github.com/symfony/mime/tree/v6.2.10" }, "funding": [ { @@ -6713,7 +6702,7 @@ "type": "tidelift" } ], - "time": "2023-02-24T10:42:00+00:00" + "time": "2023-04-19T09:54:16+00:00" }, { "name": "symfony/polyfill-ctype", @@ -7375,16 +7364,16 @@ }, { "name": "symfony/process", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "680e8a2ea6b3f87aecc07a6a65a203ae573d1902" + "reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/680e8a2ea6b3f87aecc07a6a65a203ae573d1902", - "reference": "680e8a2ea6b3f87aecc07a6a65a203ae573d1902", + "url": "https://api.github.com/repos/symfony/process/zipball/b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e", + "reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e", "shasum": "" }, "require": { @@ -7416,7 +7405,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.2.7" + "source": "https://github.com/symfony/process/tree/v6.2.10" }, "funding": [ { @@ -7432,20 +7421,20 @@ "type": "tidelift" } ], - "time": "2023-02-24T10:42:00+00:00" + "time": "2023-04-18T13:56:57+00:00" }, { "name": "symfony/routing", - "version": "v6.2.7", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "fa643fa4c56de161f8bc8c0492a76a60140b50e4" + "reference": "69062e2823f03b82265d73a966999660f0e1e404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/fa643fa4c56de161f8bc8c0492a76a60140b50e4", - "reference": "fa643fa4c56de161f8bc8c0492a76a60140b50e4", + "url": "https://api.github.com/repos/symfony/routing/zipball/69062e2823f03b82265d73a966999660f0e1e404", + "reference": "69062e2823f03b82265d73a966999660f0e1e404", "shasum": "" }, "require": { @@ -7504,7 +7493,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.2.7" + "source": "https://github.com/symfony/routing/tree/v6.2.8" }, "funding": [ { @@ -7520,7 +7509,7 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:53:37+00:00" + "time": "2023-03-14T15:00:05+00:00" }, { "name": "symfony/service-contracts", @@ -7609,16 +7598,16 @@ }, { "name": "symfony/string", - "version": "v6.2.7", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "67b8c1eec78296b85dc1c7d9743830160218993d" + "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/67b8c1eec78296b85dc1c7d9743830160218993d", - "reference": "67b8c1eec78296b85dc1c7d9743830160218993d", + "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef", + "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef", "shasum": "" }, "require": { @@ -7675,7 +7664,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.7" + "source": "https://github.com/symfony/string/tree/v6.2.8" }, "funding": [ { @@ -7691,20 +7680,20 @@ "type": "tidelift" } ], - "time": "2023-02-24T10:42:00+00:00" + "time": "2023-03-20T16:06:02+00:00" }, { "name": "symfony/translation", - "version": "v6.2.7", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "90db1c6138c90527917671cd9ffa9e8b359e3a73" + "reference": "817535dbb1721df8b3a8f2489dc7e50bcd6209b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/90db1c6138c90527917671cd9ffa9e8b359e3a73", - "reference": "90db1c6138c90527917671cd9ffa9e8b359e3a73", + "url": "https://api.github.com/repos/symfony/translation/zipball/817535dbb1721df8b3a8f2489dc7e50bcd6209b5", + "reference": "817535dbb1721df8b3a8f2489dc7e50bcd6209b5", "shasum": "" }, "require": { @@ -7773,7 +7762,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.2.7" + "source": "https://github.com/symfony/translation/tree/v6.2.8" }, "funding": [ { @@ -7789,7 +7778,7 @@ "type": "tidelift" } ], - "time": "2023-02-24T10:42:00+00:00" + "time": "2023-03-31T09:14:44+00:00" }, { "name": "symfony/translation-contracts", @@ -7948,16 +7937,16 @@ }, { "name": "symfony/var-dumper", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e" + "reference": "41a750a23412ca76fdbbf5096943b4134272c1ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e", - "reference": "cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41a750a23412ca76fdbbf5096943b4134272c1ab", + "reference": "41a750a23412ca76fdbbf5096943b4134272c1ab", "shasum": "" }, "require": { @@ -8016,7 +8005,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.2.7" + "source": "https://github.com/symfony/var-dumper/tree/v6.2.10" }, "funding": [ { @@ -8032,7 +8021,7 @@ "type": "tidelift" } ], - "time": "2023-02-24T10:42:00+00:00" + "time": "2023-04-18T13:46:08+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8614,16 +8603,16 @@ }, { "name": "filp/whoops", - "version": "2.15.1", + "version": "2.15.2", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "e864ac957acd66e1565f25efda61e37791a5db0b" + "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/e864ac957acd66e1565f25efda61e37791a5db0b", - "reference": "e864ac957acd66e1565f25efda61e37791a5db0b", + "url": "https://api.github.com/repos/filp/whoops/zipball/aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", + "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", "shasum": "" }, "require": { @@ -8673,7 +8662,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.1" + "source": "https://github.com/filp/whoops/tree/2.15.2" }, "funding": [ { @@ -8681,7 +8670,7 @@ "type": "github" } ], - "time": "2023-03-06T18:09:13+00:00" + "time": "2023-04-12T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8736,16 +8725,16 @@ }, { "name": "laravel/sail", - "version": "v1.21.3", + "version": "v1.21.5", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "3042ff8cf403817c340d5a7762b2d32900239f46" + "reference": "27af207bb1c53faddcba34c7528b3e969f6a646d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/3042ff8cf403817c340d5a7762b2d32900239f46", - "reference": "3042ff8cf403817c340d5a7762b2d32900239f46", + "url": "https://api.github.com/repos/laravel/sail/zipball/27af207bb1c53faddcba34c7528b3e969f6a646d", + "reference": "27af207bb1c53faddcba34c7528b3e969f6a646d", "shasum": "" }, "require": { @@ -8797,7 +8786,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2023-03-13T01:22:10+00:00" + "time": "2023-04-24T13:29:38+00:00" }, { "name": "maximebf/debugbar", @@ -9515,16 +9504,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.6", + "version": "9.6.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115" + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", "shasum": "" }, "require": { @@ -9598,7 +9587,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" }, "funding": [ { @@ -9614,7 +9603,7 @@ "type": "tidelift" } ], - "time": "2023-03-27T11:43:46+00:00" + "time": "2023-04-14T08:58:40+00:00" }, { "name": "sebastian/cli-parser", @@ -10644,16 +10633,16 @@ }, { "name": "spatie/flare-client-php", - "version": "1.3.5", + "version": "1.3.6", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "3e5dd5ac4928f3d2d036bd02de5eb83fd0ef1f42" + "reference": "530ac81255af79f114344286e4275f8869c671e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/3e5dd5ac4928f3d2d036bd02de5eb83fd0ef1f42", - "reference": "3e5dd5ac4928f3d2d036bd02de5eb83fd0ef1f42", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/530ac81255af79f114344286e4275f8869c671e2", + "reference": "530ac81255af79f114344286e4275f8869c671e2", "shasum": "" }, "require": { @@ -10701,7 +10690,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.3.5" + "source": "https://github.com/spatie/flare-client-php/tree/1.3.6" }, "funding": [ { @@ -10709,42 +10698,51 @@ "type": "github" } ], - "time": "2023-01-23T15:58:46+00:00" + "time": "2023-04-12T07:57:12+00:00" }, { "name": "spatie/ignition", - "version": "1.4.5", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "cc09114b7057bd217b676f047544b33f5b6247e6" + "reference": "fbcfcabc44e506e40c4d72fd4ddf465e272a600e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/cc09114b7057bd217b676f047544b33f5b6247e6", - "reference": "cc09114b7057bd217b676f047544b33f5b6247e6", + "url": "https://api.github.com/repos/spatie/ignition/zipball/fbcfcabc44e506e40c4d72fd4ddf465e272a600e", + "reference": "fbcfcabc44e506e40c4d72fd4ddf465e272a600e", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", "php": "^8.0", + "spatie/backtrace": "^1.4", "spatie/flare-client-php": "^1.1", "symfony/console": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0" }, "require-dev": { + "illuminate/cache": "^9.52", "mockery/mockery": "^1.4", "pestphp/pest": "^1.20", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "symfony/process": "^5.4|^6.0" + "psr/simple-cache-implementation": "*", + "symfony/cache": "^6.2", + "symfony/process": "^5.4|^6.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.4.x-dev" + "dev-main": "1.5.x-dev" } }, "autoload": { @@ -10783,7 +10781,7 @@ "type": "github" } ], - "time": "2023-02-28T16:49:47+00:00" + "time": "2023-04-27T08:40:07+00:00" }, { "name": "spatie/laravel-ignition", @@ -10877,16 +10875,16 @@ }, { "name": "symfony/yaml", - "version": "v6.2.7", + "version": "v6.2.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "e8e6a1d59e050525f27a1f530aa9703423cb7f57" + "reference": "61916f3861b1e9705b18cfde723921a71dd1559d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/e8e6a1d59e050525f27a1f530aa9703423cb7f57", - "reference": "e8e6a1d59e050525f27a1f530aa9703423cb7f57", + "url": "https://api.github.com/repos/symfony/yaml/zipball/61916f3861b1e9705b18cfde723921a71dd1559d", + "reference": "61916f3861b1e9705b18cfde723921a71dd1559d", "shasum": "" }, "require": { @@ -10931,7 +10929,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.2.7" + "source": "https://github.com/symfony/yaml/tree/v6.2.10" }, "funding": [ { @@ -10947,7 +10945,7 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:57:23+00:00" + "time": "2023-04-28T13:25:36+00:00" }, { "name": "theseer/tokenizer", @@ -11013,5 +11011,5 @@ "platform-overrides": { "php": "8.1" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.1.0" } From a4b56fa358d45ae7a0490f6ac6b141ae9d5eef32 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sat, 29 Apr 2023 23:35:08 +0200 Subject: [PATCH 096/514] update datatables --- .../plugins/datatables/jquery.dataTables.js | 15383 ---------------- .../datatables/jquery.dataTables.min.css | 1 + .../datatables/jquery.dataTables.min.js | 172 +- themes/default/views/layouts/main.blade.php | 4 +- 4 files changed, 7 insertions(+), 15553 deletions(-) delete mode 100644 public/plugins/datatables/jquery.dataTables.js create mode 100644 public/plugins/datatables/jquery.dataTables.min.css diff --git a/public/plugins/datatables/jquery.dataTables.js b/public/plugins/datatables/jquery.dataTables.js deleted file mode 100644 index df26319a5..000000000 --- a/public/plugins/datatables/jquery.dataTables.js +++ /dev/null @@ -1,15383 +0,0 @@ -/*! DataTables 1.10.23 - * ©2008-2020 SpryMedia Ltd - datatables.net/license - */ - -/** - * @summary DataTables - * @description Paginate, search and order HTML tables - * @version 1.10.23 - * @file jquery.dataTables.js - * @author SpryMedia Ltd - * @contact www.datatables.net - * @copyright Copyright 2008-2020 SpryMedia Ltd. - * - * This source file is free software, available under the following license: - * MIT license - http://datatables.net/license - * - * This source file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. - * - * For details please refer to: http://www.datatables.net - */ - -/*jslint evil: true, undef: true, browser: true */ -/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ - -(function( factory ) { - "use strict"; - - if ( typeof define === 'function' && define.amd ) { - // AMD - define( ['jquery'], function ( $ ) { - return factory( $, window, document ); - } ); - } - else if ( typeof exports === 'object' ) { - // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - // CommonJS environments without a window global must pass a - // root. This will give an error otherwise - root = window; - } - - if ( ! $ ) { - $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window - require('jquery') : - require('jquery')( root ); - } - - return factory( $, root, root.document ); - }; - } - else { - // Browser - factory( jQuery, window, document ); - } -} -(function( $, window, document, undefined ) { - "use strict"; - - /** - * DataTables is a plug-in for the jQuery Javascript library. It is a highly - * flexible tool, based upon the foundations of progressive enhancement, - * which will add advanced interaction controls to any HTML table. For a - * full list of features please refer to - * [DataTables.net](href="http://datatables.net). - * - * Note that the `DataTable` object is not a global variable but is aliased - * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may - * be accessed. - * - * @class - * @param {object} [init={}] Configuration object for DataTables. Options - * are defined by {@link DataTable.defaults} - * @requires jQuery 1.7+ - * - * @example - * // Basic initialisation - * $(document).ready( function { - * $('#example').dataTable(); - * } ); - * - * @example - * // Initialisation with configuration options - in this case, disable - * // pagination and sorting. - * $(document).ready( function { - * $('#example').dataTable( { - * "paginate": false, - * "sort": false - * } ); - * } ); - */ - var DataTable = function ( options ) - { - /** - * Perform a jQuery selector action on the table's TR elements (from the tbody) and - * return the resulting jQuery object. - * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on - * @param {object} [oOpts] Optional parameters for modifying the rows to be included - * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter - * criterion ("applied") or all TR elements (i.e. no filter). - * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. - * Can be either 'current', whereby the current sorting of the table is used, or - * 'original' whereby the original order the data was read into the table is used. - * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page - * ("current") or not ("all"). If 'current' is given, then order is assumed to be - * 'current' and filter is 'applied', regardless of what they might be given as. - * @returns {object} jQuery object, filtered by the given selector. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Highlight every second row - * oTable.$('tr:odd').css('backgroundColor', 'blue'); - * } ); - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Filter to rows with 'Webkit' in them, add a background colour and then - * // remove the filter, thus highlighting the 'Webkit' rows only. - * oTable.fnFilter('Webkit'); - * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); - * oTable.fnFilter(''); - * } ); - */ - this.$ = function ( sSelector, oOpts ) - { - return this.api(true).$( sSelector, oOpts ); - }; - - - /** - * Almost identical to $ in operation, but in this case returns the data for the matched - * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes - * rather than any descendants, so the data can be obtained for the row/cell. If matching - * rows are found, the data returned is the original data array/object that was used to - * create the row (or a generated array if from a DOM source). - * - * This method is often useful in-combination with $ where both functions are given the - * same parameters and the array indexes will match identically. - * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on - * @param {object} [oOpts] Optional parameters for modifying the rows to be included - * @param {string} [oOpts.filter=none] Select elements that meet the current filter - * criterion ("applied") or all elements (i.e. no filter). - * @param {string} [oOpts.order=current] Order of the data in the processed array. - * Can be either 'current', whereby the current sorting of the table is used, or - * 'original' whereby the original order the data was read into the table is used. - * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page - * ("current") or not ("all"). If 'current' is given, then order is assumed to be - * 'current' and filter is 'applied', regardless of what they might be given as. - * @returns {array} Data for the matched elements. If any elements, as a result of the - * selector, were not TR, TD or TH elements in the DataTable, they will have a null - * entry in the array. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Get the data from the first row in the table - * var data = oTable._('tr:first'); - * - * // Do something useful with the data - * alert( "First cell is: "+data[0] ); - * } ); - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Filter to 'Webkit' and get all data for - * oTable.fnFilter('Webkit'); - * var data = oTable._('tr', {"search": "applied"}); - * - * // Do something with the data - * alert( data.length+" rows matched the search" ); - * } ); - */ - this._ = function ( sSelector, oOpts ) - { - return this.api(true).rows( sSelector, oOpts ).data(); - }; - - - /** - * Create a DataTables Api instance, with the currently selected tables for - * the Api's context. - * @param {boolean} [traditional=false] Set the API instance's context to be - * only the table referred to by the `DataTable.ext.iApiIndex` option, as was - * used in the API presented by DataTables 1.9- (i.e. the traditional mode), - * or if all tables captured in the jQuery object should be used. - * @return {DataTables.Api} - */ - this.api = function ( traditional ) - { - return traditional ? - new _Api( - _fnSettingsFromNode( this[ _ext.iApiIndex ] ) - ) : - new _Api( this ); - }; - - - /** - * Add a single new row or multiple rows of data to the table. Please note - * that this is suitable for client-side processing only - if you are using - * server-side processing (i.e. "bServerSide": true), then to add data, you - * must add it to the data source, i.e. the server-side, through an Ajax call. - * @param {array|object} data The data to be added to the table. This can be: - * <ul> - * <li>1D array of data - add a single row with the data provided</li> - * <li>2D array of arrays - add multiple rows in a single call</li> - * <li>object - data object when using <i>mData</i></li> - * <li>array of objects - multiple data objects when using <i>mData</i></li> - * </ul> - * @param {bool} [redraw=true] redraw the table or not - * @returns {array} An array of integers, representing the list of indexes in - * <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to - * the table. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * // Global var for counter - * var giCount = 2; - * - * $(document).ready(function() { - * $('#example').dataTable(); - * } ); - * - * function fnClickAddRow() { - * $('#example').dataTable().fnAddData( [ - * giCount+".1", - * giCount+".2", - * giCount+".3", - * giCount+".4" ] - * ); - * - * giCount++; - * } - */ - this.fnAddData = function( data, redraw ) - { - var api = this.api( true ); - - /* Check if we want to add multiple rows or not */ - var rows = Array.isArray(data) && ( Array.isArray(data[0]) || $.isPlainObject(data[0]) ) ? - api.rows.add( data ) : - api.row.add( data ); - - if ( redraw === undefined || redraw ) { - api.draw(); - } - - return rows.flatten().toArray(); - }; - - - /** - * This function will make DataTables recalculate the column sizes, based on the data - * contained in the table and the sizes applied to the columns (in the DOM, CSS or - * through the sWidth parameter). This can be useful when the width of the table's - * parent element changes (for example a window resize). - * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable( { - * "sScrollY": "200px", - * "bPaginate": false - * } ); - * - * $(window).on('resize', function () { - * oTable.fnAdjustColumnSizing(); - * } ); - * } ); - */ - this.fnAdjustColumnSizing = function ( bRedraw ) - { - var api = this.api( true ).columns.adjust(); - var settings = api.settings()[0]; - var scroll = settings.oScroll; - - if ( bRedraw === undefined || bRedraw ) { - api.draw( false ); - } - else if ( scroll.sX !== "" || scroll.sY !== "" ) { - /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ - _fnScrollDraw( settings ); - } - }; - - - /** - * Quickly and simply clear a table - * @param {bool} [bRedraw=true] redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) - * oTable.fnClearTable(); - * } ); - */ - this.fnClearTable = function( bRedraw ) - { - var api = this.api( true ).clear(); - - if ( bRedraw === undefined || bRedraw ) { - api.draw(); - } - }; - - - /** - * The exact opposite of 'opening' a row, this function will close any rows which - * are currently 'open'. - * @param {node} nTr the table row to 'close' - * @returns {int} 0 on success, or 1 if failed (can't find the row) - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnClose = function( nTr ) - { - this.api( true ).row( nTr ).child.hide(); - }; - - - /** - * Remove a row for the table - * @param {mixed} target The index of the row from aoData to be deleted, or - * the TR element you want to delete - * @param {function|null} [callBack] Callback function - * @param {bool} [redraw=true] Redraw the table or not - * @returns {array} The row that was deleted - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Immediately remove the first row - * oTable.fnDeleteRow( 0 ); - * } ); - */ - this.fnDeleteRow = function( target, callback, redraw ) - { - var api = this.api( true ); - var rows = api.rows( target ); - var settings = rows.settings()[0]; - var data = settings.aoData[ rows[0][0] ]; - - rows.remove(); - - if ( callback ) { - callback.call( this, settings, data ); - } - - if ( redraw === undefined || redraw ) { - api.draw(); - } - - return data; - }; - - - /** - * Restore the table to it's original state in the DOM by removing all of DataTables - * enhancements, alterations to the DOM structure of the table and event listeners. - * @param {boolean} [remove=false] Completely remove the table from the DOM - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * // This example is fairly pointless in reality, but shows how fnDestroy can be used - * var oTable = $('#example').dataTable(); - * oTable.fnDestroy(); - * } ); - */ - this.fnDestroy = function ( remove ) - { - this.api( true ).destroy( remove ); - }; - - - /** - * Redraw the table - * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) - * oTable.fnDraw(); - * } ); - */ - this.fnDraw = function( complete ) - { - // Note that this isn't an exact match to the old call to _fnDraw - it takes - // into account the new data, but can hold position. - this.api( true ).draw( complete ); - }; - - - /** - * Filter the input based on data - * @param {string} sInput String to filter the table on - * @param {int|null} [iColumn] Column to limit filtering to - * @param {bool} [bRegex=false] Treat as regular expression or not - * @param {bool} [bSmart=true] Perform smart filtering or not - * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) - * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sometime later - filter... - * oTable.fnFilter( 'test string' ); - * } ); - */ - this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) - { - var api = this.api( true ); - - if ( iColumn === null || iColumn === undefined ) { - api.search( sInput, bRegex, bSmart, bCaseInsensitive ); - } - else { - api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive ); - } - - api.draw(); - }; - - - /** - * Get the data for the whole table, an individual row or an individual cell based on the - * provided parameters. - * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as - * a TR node then the data source for the whole row will be returned. If given as a - * TD/TH cell node then iCol will be automatically calculated and the data for the - * cell returned. If given as an integer, then this is treated as the aoData internal - * data index for the row (see fnGetPosition) and the data for that row used. - * @param {int} [col] Optional column index that you want the data of. - * @returns {array|object|string} If mRow is undefined, then the data for all rows is - * returned. If mRow is defined, just data for that row, and is iCol is - * defined, only data for the designated cell is returned. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * // Row data - * $(document).ready(function() { - * oTable = $('#example').dataTable(); - * - * oTable.$('tr').click( function () { - * var data = oTable.fnGetData( this ); - * // ... do something with the array / object of data for the row - * } ); - * } ); - * - * @example - * // Individual cell data - * $(document).ready(function() { - * oTable = $('#example').dataTable(); - * - * oTable.$('td').click( function () { - * var sData = oTable.fnGetData( this ); - * alert( 'The cell clicked on had the value of '+sData ); - * } ); - * } ); - */ - this.fnGetData = function( src, col ) - { - var api = this.api( true ); - - if ( src !== undefined ) { - var type = src.nodeName ? src.nodeName.toLowerCase() : ''; - - return col !== undefined || type == 'td' || type == 'th' ? - api.cell( src, col ).data() : - api.row( src ).data() || null; - } - - return api.data().toArray(); - }; - - - /** - * Get an array of the TR nodes that are used in the table's body. Note that you will - * typically want to use the '$' API method in preference to this as it is more - * flexible. - * @param {int} [iRow] Optional row index for the TR element you want - * @returns {array|node} If iRow is undefined, returns an array of all TR elements - * in the table's body, or iRow is defined, just the TR element requested. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Get the nodes from the table - * var nNodes = oTable.fnGetNodes( ); - * } ); - */ - this.fnGetNodes = function( iRow ) - { - var api = this.api( true ); - - return iRow !== undefined ? - api.row( iRow ).node() : - api.rows().nodes().flatten().toArray(); - }; - - - /** - * Get the array indexes of a particular cell from it's DOM element - * and column index including hidden columns - * @param {node} node this can either be a TR, TD or TH in the table's body - * @returns {int} If nNode is given as a TR, then a single index is returned, or - * if given as a cell, an array of [row index, column index (visible), - * column index (all)] is given. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * $('#example tbody td').click( function () { - * // Get the position of the current data from the node - * var aPos = oTable.fnGetPosition( this ); - * - * // Get the data array for this row - * var aData = oTable.fnGetData( aPos[0] ); - * - * // Update the data array and return the value - * aData[ aPos[1] ] = 'clicked'; - * this.innerHTML = 'clicked'; - * } ); - * - * // Init DataTables - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnGetPosition = function( node ) - { - var api = this.api( true ); - var nodeName = node.nodeName.toUpperCase(); - - if ( nodeName == 'TR' ) { - return api.row( node ).index(); - } - else if ( nodeName == 'TD' || nodeName == 'TH' ) { - var cell = api.cell( node ).index(); - - return [ - cell.row, - cell.columnVisible, - cell.column - ]; - } - return null; - }; - - - /** - * Check to see if a row is 'open' or not. - * @param {node} nTr the table row to check - * @returns {boolean} true if the row is currently open, false otherwise - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnIsOpen = function( nTr ) - { - return this.api( true ).row( nTr ).child.isShown(); - }; - - - /** - * This function will place a new row directly after a row which is currently - * on display on the page, with the HTML contents that is passed into the - * function. This can be used, for example, to ask for confirmation that a - * particular record should be deleted. - * @param {node} nTr The table row to 'open' - * @param {string|node|jQuery} mHtml The HTML to put into the row - * @param {string} sClass Class to give the new TD cell - * @returns {node} The row opened. Note that if the table row passed in as the - * first parameter, is not found in the table, this method will silently - * return. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnOpen = function( nTr, mHtml, sClass ) - { - return this.api( true ) - .row( nTr ) - .child( mHtml, sClass ) - .show() - .child()[0]; - }; - - - /** - * Change the pagination - provides the internal logic for pagination in a simple API - * function. With this function you can have a DataTables table go to the next, - * previous, first or last pages. - * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" - * or page number to jump to (integer), note that page 0 is the first page. - * @param {bool} [bRedraw=true] Redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * oTable.fnPageChange( 'next' ); - * } ); - */ - this.fnPageChange = function ( mAction, bRedraw ) - { - var api = this.api( true ).page( mAction ); - - if ( bRedraw === undefined || bRedraw ) { - api.draw(false); - } - }; - - - /** - * Show a particular column - * @param {int} iCol The column whose display should be changed - * @param {bool} bShow Show (true) or hide (false) the column - * @param {bool} [bRedraw=true] Redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Hide the second column after initialisation - * oTable.fnSetColumnVis( 1, false ); - * } ); - */ - this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) - { - var api = this.api( true ).column( iCol ).visible( bShow ); - - if ( bRedraw === undefined || bRedraw ) { - api.columns.adjust().draw(); - } - }; - - - /** - * Get the settings for a particular table for external manipulation - * @returns {object} DataTables settings object. See - * {@link DataTable.models.oSettings} - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * var oSettings = oTable.fnSettings(); - * - * // Show an example parameter from the settings - * alert( oSettings._iDisplayStart ); - * } ); - */ - this.fnSettings = function() - { - return _fnSettingsFromNode( this[_ext.iApiIndex] ); - }; - - - /** - * Sort the table by a particular column - * @param {int} iCol the data index to sort on. Note that this will not match the - * 'display index' if you have hidden data entries - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sort immediately with columns 0 and 1 - * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); - * } ); - */ - this.fnSort = function( aaSort ) - { - this.api( true ).order( aaSort ).draw(); - }; - - - /** - * Attach a sort listener to an element for a given column - * @param {node} nNode the element to attach the sort listener to - * @param {int} iColumn the column that a click on this node will sort on - * @param {function} [fnCallback] callback function when sort is run - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sort on column 1, when 'sorter' is clicked on - * oTable.fnSortListener( document.getElementById('sorter'), 1 ); - * } ); - */ - this.fnSortListener = function( nNode, iColumn, fnCallback ) - { - this.api( true ).order.listener( nNode, iColumn, fnCallback ); - }; - - - /** - * Update a table cell or row - this method will accept either a single value to - * update the cell with, an array of values with one element for each column or - * an object in the same format as the original data source. The function is - * self-referencing in order to make the multi column updates easier. - * @param {object|array|string} mData Data to update the cell/row with - * @param {node|int} mRow TR element you want to update or the aoData index - * @param {int} [iColumn] The column to update, give as null or undefined to - * update a whole row. - * @param {bool} [bRedraw=true] Redraw the table or not - * @param {bool} [bAction=true] Perform pre-draw actions or not - * @returns {int} 0 on success, 1 on error - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell - * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row - * } ); - */ - this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) - { - var api = this.api( true ); - - if ( iColumn === undefined || iColumn === null ) { - api.row( mRow ).data( mData ); - } - else { - api.cell( mRow, iColumn ).data( mData ); - } - - if ( bAction === undefined || bAction ) { - api.columns.adjust(); - } - - if ( bRedraw === undefined || bRedraw ) { - api.draw(); - } - return 0; - }; - - - /** - * Provide a common method for plug-ins to check the version of DataTables being used, in order - * to ensure compatibility. - * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the - * formats "X" and "X.Y" are also acceptable. - * @returns {boolean} true if this version of DataTables is greater or equal to the required - * version, or false if this version of DataTales is not suitable - * @method - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * alert( oTable.fnVersionCheck( '1.9.0' ) ); - * } ); - */ - this.fnVersionCheck = _ext.fnVersionCheck; - - - var _that = this; - var emptyInit = options === undefined; - var len = this.length; - - if ( emptyInit ) { - options = {}; - } - - this.oApi = this.internal = _ext.internal; - - // Extend with old style plug-in API methods - for ( var fn in DataTable.ext.internal ) { - if ( fn ) { - this[fn] = _fnExternApiFunc(fn); - } - } - - this.each(function() { - // For each initialisation we want to give it a clean initialisation - // object that can be bashed around - var o = {}; - var oInit = len > 1 ? // optimisation for single table case - _fnExtend( o, options, true ) : - options; - - /*global oInit,_that,emptyInit*/ - var i=0, iLen, j, jLen, k, kLen; - var sId = this.getAttribute( 'id' ); - var bInitHandedOff = false; - var defaults = DataTable.defaults; - var $this = $(this); - - - /* Sanity check */ - if ( this.nodeName.toLowerCase() != 'table' ) - { - _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); - return; - } - - /* Backwards compatibility for the defaults */ - _fnCompatOpts( defaults ); - _fnCompatCols( defaults.column ); - - /* Convert the camel-case defaults to Hungarian */ - _fnCamelToHungarian( defaults, defaults, true ); - _fnCamelToHungarian( defaults.column, defaults.column, true ); - - /* Setting up the initialisation object */ - _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); - - - - /* Check to see if we are re-initialising a table */ - var allSettings = DataTable.settings; - for ( i=0, iLen=allSettings.length ; i<iLen ; i++ ) - { - var s = allSettings[i]; - - /* Base check on table node */ - if ( - s.nTable == this || - (s.nTHead && s.nTHead.parentNode == this) || - (s.nTFoot && s.nTFoot.parentNode == this) - ) { - var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve; - var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; - - if ( emptyInit || bRetrieve ) - { - return s.oInstance; - } - else if ( bDestroy ) - { - s.oInstance.fnDestroy(); - break; - } - else - { - _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 ); - return; - } - } - - /* If the element we are initialising has the same ID as a table which was previously - * initialised, but the table nodes don't match (from before) then we destroy the old - * instance by simply deleting it. This is under the assumption that the table has been - * destroyed by other methods. Anyone using non-id selectors will need to do this manually - */ - if ( s.sTableId == this.id ) - { - allSettings.splice( i, 1 ); - break; - } - } - - /* Ensure the table has an ID - required for accessibility */ - if ( sId === null || sId === "" ) - { - sId = "DataTables_Table_"+(DataTable.ext._unique++); - this.id = sId; - } - - /* Create the settings object for this table and set some of the default parameters */ - var oSettings = $.extend( true, {}, DataTable.models.oSettings, { - "sDestroyWidth": $this[0].style.width, - "sInstance": sId, - "sTableId": sId - } ); - oSettings.nTable = this; - oSettings.oApi = _that.internal; - oSettings.oInit = oInit; - - allSettings.push( oSettings ); - - // Need to add the instance after the instance after the settings object has been added - // to the settings array, so we can self reference the table instance if more than one - oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); - - // Backwards compatibility, before we apply all the defaults - _fnCompatOpts( oInit ); - _fnLanguageCompat( oInit.oLanguage ); - - // If the length menu is given, but the init display length is not, use the length menu - if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) - { - oInit.iDisplayLength = Array.isArray( oInit.aLengthMenu[0] ) ? - oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0]; - } - - // Apply the defaults and init options to make a single init object will all - // options defined from defaults and instance options. - oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); - - - // Map the initialisation options onto the settings object - _fnMap( oSettings.oFeatures, oInit, [ - "bPaginate", - "bLengthChange", - "bFilter", - "bSort", - "bSortMulti", - "bInfo", - "bProcessing", - "bAutoWidth", - "bSortClasses", - "bServerSide", - "bDeferRender" - ] ); - _fnMap( oSettings, oInit, [ - "asStripeClasses", - "ajax", - "fnServerData", - "fnFormatNumber", - "sServerMethod", - "aaSorting", - "aaSortingFixed", - "aLengthMenu", - "sPaginationType", - "sAjaxSource", - "sAjaxDataProp", - "iStateDuration", - "sDom", - "bSortCellsTop", - "iTabIndex", - "fnStateLoadCallback", - "fnStateSaveCallback", - "renderer", - "searchDelay", - "rowId", - [ "iCookieDuration", "iStateDuration" ], // backwards compat - [ "oSearch", "oPreviousSearch" ], - [ "aoSearchCols", "aoPreSearchCols" ], - [ "iDisplayLength", "_iDisplayLength" ] - ] ); - _fnMap( oSettings.oScroll, oInit, [ - [ "sScrollX", "sX" ], - [ "sScrollXInner", "sXInner" ], - [ "sScrollY", "sY" ], - [ "bScrollCollapse", "bCollapse" ] - ] ); - _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); - - /* Callback functions which are array driven */ - _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback, 'user' ); - _fnCallbackReg( oSettings, 'aoServerParams', oInit.fnServerParams, 'user' ); - _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams, 'user' ); - _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams, 'user' ); - _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded, 'user' ); - _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback, 'user' ); - _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow, 'user' ); - _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback, 'user' ); - _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback, 'user' ); - _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete, 'user' ); - _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback, 'user' ); - - oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); - - /* Browser support detection */ - _fnBrowserDetect( oSettings ); - - var oClasses = oSettings.oClasses; - - $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); - $this.addClass( oClasses.sTable ); - - - if ( oSettings.iInitDisplayStart === undefined ) - { - /* Display start point, taking into account the save saving */ - oSettings.iInitDisplayStart = oInit.iDisplayStart; - oSettings._iDisplayStart = oInit.iDisplayStart; - } - - if ( oInit.iDeferLoading !== null ) - { - oSettings.bDeferLoading = true; - var tmp = Array.isArray( oInit.iDeferLoading ); - oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading; - oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading; - } - - /* Language definitions */ - var oLanguage = oSettings.oLanguage; - $.extend( true, oLanguage, oInit.oLanguage ); - - if ( oLanguage.sUrl ) - { - /* Get the language definitions from a file - because this Ajax call makes the language - * get async to the remainder of this function we use bInitHandedOff to indicate that - * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor - */ - $.ajax( { - dataType: 'json', - url: oLanguage.sUrl, - success: function ( json ) { - _fnLanguageCompat( json ); - _fnCamelToHungarian( defaults.oLanguage, json ); - $.extend( true, oLanguage, json ); - _fnInitialise( oSettings ); - }, - error: function () { - // Error occurred loading language file, continue on as best we can - _fnInitialise( oSettings ); - } - } ); - bInitHandedOff = true; - } - - /* - * Stripes - */ - if ( oInit.asStripeClasses === null ) - { - oSettings.asStripeClasses =[ - oClasses.sStripeOdd, - oClasses.sStripeEven - ]; - } - - /* Remove row stripe classes if they are already on the table row */ - var stripeClasses = oSettings.asStripeClasses; - var rowOne = $this.children('tbody').find('tr').eq(0); - if ( $.inArray( true, $.map( stripeClasses, function(el, i) { - return rowOne.hasClass(el); - } ) ) !== -1 ) { - $('tbody tr', this).removeClass( stripeClasses.join(' ') ); - oSettings.asDestroyStripes = stripeClasses.slice(); - } - - /* - * Columns - * See if we should load columns automatically or use defined ones - */ - var anThs = []; - var aoColumnsInit; - var nThead = this.getElementsByTagName('thead'); - if ( nThead.length !== 0 ) - { - _fnDetectHeader( oSettings.aoHeader, nThead[0] ); - anThs = _fnGetUniqueThs( oSettings ); - } - - /* If not given a column array, generate one with nulls */ - if ( oInit.aoColumns === null ) - { - aoColumnsInit = []; - for ( i=0, iLen=anThs.length ; i<iLen ; i++ ) - { - aoColumnsInit.push( null ); - } - } - else - { - aoColumnsInit = oInit.aoColumns; - } - - /* Add the columns */ - for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ ) - { - _fnAddColumn( oSettings, anThs ? anThs[i] : null ); - } - - /* Apply the column definitions */ - _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) { - _fnColumnOptions( oSettings, iCol, oDef ); - } ); - - /* HTML5 attribute detection - build an mData object automatically if the - * attributes are found - */ - if ( rowOne.length ) { - var a = function ( cell, name ) { - return cell.getAttribute( 'data-'+name ) !== null ? name : null; - }; - - $( rowOne[0] ).children('th, td').each( function (i, cell) { - var col = oSettings.aoColumns[i]; - - if ( col.mData === i ) { - var sort = a( cell, 'sort' ) || a( cell, 'order' ); - var filter = a( cell, 'filter' ) || a( cell, 'search' ); - - if ( sort !== null || filter !== null ) { - col.mData = { - _: i+'.display', - sort: sort !== null ? i+'.@data-'+sort : undefined, - type: sort !== null ? i+'.@data-'+sort : undefined, - filter: filter !== null ? i+'.@data-'+filter : undefined - }; - - _fnColumnOptions( oSettings, i ); - } - } - } ); - } - - var features = oSettings.oFeatures; - var loadedInit = function () { - /* - * Sorting - * @todo For modularisation (1.11) this needs to do into a sort start up handler - */ - - // If aaSorting is not defined, then we use the first indicator in asSorting - // in case that has been altered, so the default sort reflects that option - if ( oInit.aaSorting === undefined ) { - var sorting = oSettings.aaSorting; - for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) { - sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0]; - } - } - - /* Do a first pass on the sorting classes (allows any size changes to be taken into - * account, and also will apply sorting disabled classes if disabled - */ - _fnSortingClasses( oSettings ); - - if ( features.bSort ) { - _fnCallbackReg( oSettings, 'aoDrawCallback', function () { - if ( oSettings.bSorted ) { - var aSort = _fnSortFlatten( oSettings ); - var sortedColumns = {}; - - $.each( aSort, function (i, val) { - sortedColumns[ val.src ] = val.dir; - } ); - - _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] ); - _fnSortAria( oSettings ); - } - } ); - } - - _fnCallbackReg( oSettings, 'aoDrawCallback', function () { - if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) { - _fnSortingClasses( oSettings ); - } - }, 'sc' ); - - - /* - * Final init - * Cache the header, body and footer as required, creating them if needed - */ - - // Work around for Webkit bug 83867 - store the caption-side before removing from doc - var captions = $this.children('caption').each( function () { - this._captionSide = $(this).css('caption-side'); - } ); - - var thead = $this.children('thead'); - if ( thead.length === 0 ) { - thead = $('<thead/>').appendTo($this); - } - oSettings.nTHead = thead[0]; - - var tbody = $this.children('tbody'); - if ( tbody.length === 0 ) { - tbody = $('<tbody/>').appendTo($this); - } - oSettings.nTBody = tbody[0]; - - var tfoot = $this.children('tfoot'); - if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) { - // If we are a scrolling table, and no footer has been given, then we need to create - // a tfoot element for the caption element to be appended to - tfoot = $('<tfoot/>').appendTo($this); - } - - if ( tfoot.length === 0 || tfoot.children().length === 0 ) { - $this.addClass( oClasses.sNoFooter ); - } - else if ( tfoot.length > 0 ) { - oSettings.nTFoot = tfoot[0]; - _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot ); - } - - /* Check if there is data passing into the constructor */ - if ( oInit.aaData ) { - for ( i=0 ; i<oInit.aaData.length ; i++ ) { - _fnAddData( oSettings, oInit.aaData[ i ] ); - } - } - else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' ) { - /* Grab the data from the page - only do this when deferred loading or no Ajax - * source since there is no point in reading the DOM data if we are then going - * to replace it with Ajax data - */ - _fnAddTr( oSettings, $(oSettings.nTBody).children('tr') ); - } - - /* Copy the data index array */ - oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); - - /* Initialisation complete - table can be drawn */ - oSettings.bInitialised = true; - - /* Check if we need to initialise the table (it might not have been handed off to the - * language processor) - */ - if ( bInitHandedOff === false ) { - _fnInitialise( oSettings ); - } - }; - - /* Must be done after everything which can be overridden by the state saving! */ - if ( oInit.bStateSave ) - { - features.bStateSave = true; - _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' ); - _fnLoadState( oSettings, oInit, loadedInit ); - } - else { - loadedInit(); - } - - } ); - _that = null; - return this; - }; - - - /* - * It is useful to have variables which are scoped locally so only the - * DataTables functions can access them and they don't leak into global space. - * At the same time these functions are often useful over multiple files in the - * core and API, so we list, or at least document, all variables which are used - * by DataTables as private variables here. This also ensures that there is no - * clashing of variable names and that they can easily referenced for reuse. - */ - - - // Defined else where - // _selector_run - // _selector_opts - // _selector_first - // _selector_row_indexes - - var _ext; // DataTable.ext - var _Api; // DataTable.Api - var _api_register; // DataTable.Api.register - var _api_registerPlural; // DataTable.Api.registerPlural - - var _re_dic = {}; - var _re_new_lines = /[\r\n\u2028]/g; - var _re_html = /<.*?>/g; - - // This is not strict ISO8601 - Date.parse() is quite lax, although - // implementations differ between browsers. - var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/; - - // Escape regular expression special characters - var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); - - // http://en.wikipedia.org/wiki/Foreign_exchange_market - // - \u20BD - Russian ruble. - // - \u20a9 - South Korean Won - // - \u20BA - Turkish Lira - // - \u20B9 - Indian Rupee - // - R - Brazil (R$) and South Africa - // - fr - Swiss Franc - // - kr - Swedish krona, Norwegian krone and Danish krone - // - \u2009 is thin space and \u202F is narrow no-break space, both used in many - // - Ƀ - Bitcoin - // - Ξ - Ethereum - // standards as thousands separators. - var _re_formatted_numeric = /['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi; - - - var _empty = function ( d ) { - return !d || d === true || d === '-' ? true : false; - }; - - - var _intVal = function ( s ) { - var integer = parseInt( s, 10 ); - return !isNaN(integer) && isFinite(s) ? integer : null; - }; - - // Convert from a formatted number with characters other than `.` as the - // decimal place, to a Javascript number - var _numToDecimal = function ( num, decimalPoint ) { - // Cache created regular expressions for speed as this function is called often - if ( ! _re_dic[ decimalPoint ] ) { - _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); - } - return typeof num === 'string' && decimalPoint !== '.' ? - num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : - num; - }; - - - var _isNumber = function ( d, decimalPoint, formatted ) { - var strType = typeof d === 'string'; - - // If empty return immediately so there must be a number if it is a - // formatted string (this stops the string "k", or "kr", etc being detected - // as a formatted number for currency - if ( _empty( d ) ) { - return true; - } - - if ( decimalPoint && strType ) { - d = _numToDecimal( d, decimalPoint ); - } - - if ( formatted && strType ) { - d = d.replace( _re_formatted_numeric, '' ); - } - - return !isNaN( parseFloat(d) ) && isFinite( d ); - }; - - - // A string without HTML in it can be considered to be HTML still - var _isHtml = function ( d ) { - return _empty( d ) || typeof d === 'string'; - }; - - - var _htmlNumeric = function ( d, decimalPoint, formatted ) { - if ( _empty( d ) ) { - return true; - } - - var html = _isHtml( d ); - return ! html ? - null : - _isNumber( _stripHtml( d ), decimalPoint, formatted ) ? - true : - null; - }; - - - var _pluck = function ( a, prop, prop2 ) { - var out = []; - var i=0, ien=a.length; - - // Could have the test in the loop for slightly smaller code, but speed - // is essential here - if ( prop2 !== undefined ) { - for ( ; i<ien ; i++ ) { - if ( a[i] && a[i][ prop ] ) { - out.push( a[i][ prop ][ prop2 ] ); - } - } - } - else { - for ( ; i<ien ; i++ ) { - if ( a[i] ) { - out.push( a[i][ prop ] ); - } - } - } - - return out; - }; - - - // Basically the same as _pluck, but rather than looping over `a` we use `order` - // as the indexes to pick from `a` - var _pluck_order = function ( a, order, prop, prop2 ) - { - var out = []; - var i=0, ien=order.length; - - // Could have the test in the loop for slightly smaller code, but speed - // is essential here - if ( prop2 !== undefined ) { - for ( ; i<ien ; i++ ) { - if ( a[ order[i] ][ prop ] ) { - out.push( a[ order[i] ][ prop ][ prop2 ] ); - } - } - } - else { - for ( ; i<ien ; i++ ) { - out.push( a[ order[i] ][ prop ] ); - } - } - - return out; - }; - - - var _range = function ( len, start ) - { - var out = []; - var end; - - if ( start === undefined ) { - start = 0; - end = len; - } - else { - end = start; - start = len; - } - - for ( var i=start ; i<end ; i++ ) { - out.push( i ); - } - - return out; - }; - - - var _removeEmpty = function ( a ) - { - var out = []; - - for ( var i=0, ien=a.length ; i<ien ; i++ ) { - if ( a[i] ) { // careful - will remove all falsy values! - out.push( a[i] ); - } - } - - return out; - }; - - - var _stripHtml = function ( d ) { - return d.replace( _re_html, '' ); - }; - - - /** - * Determine if all values in the array are unique. This means we can short - * cut the _unique method at the cost of a single loop. A sorted array is used - * to easily check the values. - * - * @param {array} src Source array - * @return {boolean} true if all unique, false otherwise - * @ignore - */ - var _areAllUnique = function ( src ) { - if ( src.length < 2 ) { - return true; - } - - var sorted = src.slice().sort(); - var last = sorted[0]; - - for ( var i=1, ien=sorted.length ; i<ien ; i++ ) { - if ( sorted[i] === last ) { - return false; - } - - last = sorted[i]; - } - - return true; - }; - - - /** - * Find the unique elements in a source array. - * - * @param {array} src Source array - * @return {array} Array of unique items - * @ignore - */ - var _unique = function ( src ) - { - if ( _areAllUnique( src ) ) { - return src.slice(); - } - - // A faster unique method is to use object keys to identify used values, - // but this doesn't work with arrays or objects, which we must also - // consider. See jsperf.com/compare-array-unique-versions/4 for more - // information. - var - out = [], - val, - i, ien=src.length, - j, k=0; - - again: for ( i=0 ; i<ien ; i++ ) { - val = src[i]; - - for ( j=0 ; j<k ; j++ ) { - if ( out[j] === val ) { - continue again; - } - } - - out.push( val ); - k++; - } - - return out; - }; - - // Surprisingly this is faster than [].concat.apply - // https://jsperf.com/flatten-an-array-loop-vs-reduce/2 - var _flatten = function (out, val) { - if (Array.isArray(val)) { - for (var i=0 ; i<val.length ; i++) { - _flatten(out, val[i]); - } - } - else { - out.push(val); - } - - return out; - } - - // Array.isArray polyfill. - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray - if (! Array.isArray) { - Array.isArray = function(arg) { - return Object.prototype.toString.call(arg) === '[object Array]'; - }; - } - - // .trim() polyfill - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim - if (!String.prototype.trim) { - String.prototype.trim = function () { - return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); - }; - } - - /** - * DataTables utility methods - * - * This namespace provides helper methods that DataTables uses internally to - * create a DataTable, but which are not exclusively used only for DataTables. - * These methods can be used by extension authors to save the duplication of - * code. - * - * @namespace - */ - DataTable.util = { - /** - * Throttle the calls to a function. Arguments and context are maintained - * for the throttled function. - * - * @param {function} fn Function to be called - * @param {integer} freq Call frequency in mS - * @return {function} Wrapped function - */ - throttle: function ( fn, freq ) { - var - frequency = freq !== undefined ? freq : 200, - last, - timer; - - return function () { - var - that = this, - now = +new Date(), - args = arguments; - - if ( last && now < last + frequency ) { - clearTimeout( timer ); - - timer = setTimeout( function () { - last = undefined; - fn.apply( that, args ); - }, frequency ); - } - else { - last = now; - fn.apply( that, args ); - } - }; - }, - - - /** - * Escape a string such that it can be used in a regular expression - * - * @param {string} val string to escape - * @returns {string} escaped string - */ - escapeRegex: function ( val ) { - return val.replace( _re_escape_regex, '\\$1' ); - } - }; - - - - /** - * Create a mapping object that allows camel case parameters to be looked up - * for their Hungarian counterparts. The mapping is stored in a private - * parameter called `_hungarianMap` which can be accessed on the source object. - * @param {object} o - * @memberof DataTable#oApi - */ - function _fnHungarianMap ( o ) - { - var - hungarian = 'a aa ai ao as b fn i m o s ', - match, - newKey, - map = {}; - - $.each( o, function (key, val) { - match = key.match(/^([^A-Z]+?)([A-Z])/); - - if ( match && hungarian.indexOf(match[1]+' ') !== -1 ) - { - newKey = key.replace( match[0], match[2].toLowerCase() ); - map[ newKey ] = key; - - if ( match[1] === 'o' ) - { - _fnHungarianMap( o[key] ); - } - } - } ); - - o._hungarianMap = map; - } - - - /** - * Convert from camel case parameters to Hungarian, based on a Hungarian map - * created by _fnHungarianMap. - * @param {object} src The model object which holds all parameters that can be - * mapped. - * @param {object} user The object to convert from camel case to Hungarian. - * @param {boolean} force When set to `true`, properties which already have a - * Hungarian value in the `user` object will be overwritten. Otherwise they - * won't be. - * @memberof DataTable#oApi - */ - function _fnCamelToHungarian ( src, user, force ) - { - if ( ! src._hungarianMap ) { - _fnHungarianMap( src ); - } - - var hungarianKey; - - $.each( user, function (key, val) { - hungarianKey = src._hungarianMap[ key ]; - - if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) ) - { - // For objects, we need to buzz down into the object to copy parameters - if ( hungarianKey.charAt(0) === 'o' ) - { - // Copy the camelCase options over to the hungarian - if ( ! user[ hungarianKey ] ) { - user[ hungarianKey ] = {}; - } - $.extend( true, user[hungarianKey], user[key] ); - - _fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force ); - } - else { - user[hungarianKey] = user[ key ]; - } - } - } ); - } - - - /** - * Language compatibility - when certain options are given, and others aren't, we - * need to duplicate the values over, in order to provide backwards compatibility - * with older language files. - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnLanguageCompat( lang ) - { - // Note the use of the Hungarian notation for the parameters in this method as - // this is called after the mapping of camelCase to Hungarian - var defaults = DataTable.defaults.oLanguage; - - // Default mapping - var defaultDecimal = defaults.sDecimal; - if ( defaultDecimal ) { - _addNumericSort( defaultDecimal ); - } - - if ( lang ) { - var zeroRecords = lang.sZeroRecords; - - // Backwards compatibility - if there is no sEmptyTable given, then use the same as - // sZeroRecords - assuming that is given. - if ( ! lang.sEmptyTable && zeroRecords && - defaults.sEmptyTable === "No data available in table" ) - { - _fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' ); - } - - // Likewise with loading records - if ( ! lang.sLoadingRecords && zeroRecords && - defaults.sLoadingRecords === "Loading..." ) - { - _fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' ); - } - - // Old parameter name of the thousands separator mapped onto the new - if ( lang.sInfoThousands ) { - lang.sThousands = lang.sInfoThousands; - } - - var decimal = lang.sDecimal; - if ( decimal && defaultDecimal !== decimal ) { - _addNumericSort( decimal ); - } - } - } - - - /** - * Map one parameter onto another - * @param {object} o Object to map - * @param {*} knew The new parameter name - * @param {*} old The old parameter name - */ - var _fnCompatMap = function ( o, knew, old ) { - if ( o[ knew ] !== undefined ) { - o[ old ] = o[ knew ]; - } - }; - - - /** - * Provide backwards compatibility for the main DT options. Note that the new - * options are mapped onto the old parameters, so this is an external interface - * change only. - * @param {object} init Object to map - */ - function _fnCompatOpts ( init ) - { - _fnCompatMap( init, 'ordering', 'bSort' ); - _fnCompatMap( init, 'orderMulti', 'bSortMulti' ); - _fnCompatMap( init, 'orderClasses', 'bSortClasses' ); - _fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' ); - _fnCompatMap( init, 'order', 'aaSorting' ); - _fnCompatMap( init, 'orderFixed', 'aaSortingFixed' ); - _fnCompatMap( init, 'paging', 'bPaginate' ); - _fnCompatMap( init, 'pagingType', 'sPaginationType' ); - _fnCompatMap( init, 'pageLength', 'iDisplayLength' ); - _fnCompatMap( init, 'searching', 'bFilter' ); - - // Boolean initialisation of x-scrolling - if ( typeof init.sScrollX === 'boolean' ) { - init.sScrollX = init.sScrollX ? '100%' : ''; - } - if ( typeof init.scrollX === 'boolean' ) { - init.scrollX = init.scrollX ? '100%' : ''; - } - - // Column search objects are in an array, so it needs to be converted - // element by element - var searchCols = init.aoSearchCols; - - if ( searchCols ) { - for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) { - if ( searchCols[i] ) { - _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] ); - } - } - } - } - - - /** - * Provide backwards compatibility for column options. Note that the new options - * are mapped onto the old parameters, so this is an external interface change - * only. - * @param {object} init Object to map - */ - function _fnCompatCols ( init ) - { - _fnCompatMap( init, 'orderable', 'bSortable' ); - _fnCompatMap( init, 'orderData', 'aDataSort' ); - _fnCompatMap( init, 'orderSequence', 'asSorting' ); - _fnCompatMap( init, 'orderDataType', 'sortDataType' ); - - // orderData can be given as an integer - var dataSort = init.aDataSort; - if ( typeof dataSort === 'number' && ! Array.isArray( dataSort ) ) { - init.aDataSort = [ dataSort ]; - } - } - - - /** - * Browser feature detection for capabilities, quirks - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnBrowserDetect( settings ) - { - // We don't need to do this every time DataTables is constructed, the values - // calculated are specific to the browser and OS configuration which we - // don't expect to change between initialisations - if ( ! DataTable.__browser ) { - var browser = {}; - DataTable.__browser = browser; - - // Scrolling feature / quirks detection - var n = $('<div/>') - .css( { - position: 'fixed', - top: 0, - left: $(window).scrollLeft()*-1, // allow for scrolling - height: 1, - width: 1, - overflow: 'hidden' - } ) - .append( - $('<div/>') - .css( { - position: 'absolute', - top: 1, - left: 1, - width: 100, - overflow: 'scroll' - } ) - .append( - $('<div/>') - .css( { - width: '100%', - height: 10 - } ) - ) - ) - .appendTo( 'body' ); - - var outer = n.children(); - var inner = outer.children(); - - // Numbers below, in order, are: - // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth - // - // IE6 XP: 100 100 100 83 - // IE7 Vista: 100 100 100 83 - // IE 8+ Windows: 83 83 100 83 - // Evergreen Windows: 83 83 100 83 - // Evergreen Mac with scrollbars: 85 85 100 85 - // Evergreen Mac without scrollbars: 100 100 100 100 - - // Get scrollbar width - browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; - - // IE6/7 will oversize a width 100% element inside a scrolling element, to - // include the width of the scrollbar, while other browsers ensure the inner - // element is contained without forcing scrolling - browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; - - // In rtl text layout, some browsers (most, but not all) will place the - // scrollbar on the left, rather than the right. - browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; - - // IE8- don't provide height and width for getBoundingClientRect - browser.bBounding = n[0].getBoundingClientRect().width ? true : false; - - n.remove(); - } - - $.extend( settings.oBrowser, DataTable.__browser ); - settings.oScroll.iBarWidth = DataTable.__browser.barWidth; - } - - - /** - * Array.prototype reduce[Right] method, used for browsers which don't support - * JS 1.6. Done this way to reduce code size, since we iterate either way - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnReduce ( that, fn, init, start, end, inc ) - { - var - i = start, - value, - isSet = false; - - if ( init !== undefined ) { - value = init; - isSet = true; - } - - while ( i !== end ) { - if ( ! that.hasOwnProperty(i) ) { - continue; - } - - value = isSet ? - fn( value, that[i], i, that ) : - that[i]; - - isSet = true; - i += inc; - } - - return value; - } - - /** - * Add a column to the list used for the table with default values - * @param {object} oSettings dataTables settings object - * @param {node} nTh The th element for this column - * @memberof DataTable#oApi - */ - function _fnAddColumn( oSettings, nTh ) - { - // Add column to aoColumns array - var oDefaults = DataTable.defaults.column; - var iCol = oSettings.aoColumns.length; - var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { - "nTh": nTh ? nTh : document.createElement('th'), - "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', - "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], - "mData": oDefaults.mData ? oDefaults.mData : iCol, - idx: iCol - } ); - oSettings.aoColumns.push( oCol ); - - // Add search object for column specific search. Note that the `searchCols[ iCol ]` - // passed into extend can be undefined. This allows the user to give a default - // with only some of the parameters defined, and also not give a default - var searchCols = oSettings.aoPreSearchCols; - searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); - - // Use the default column options function to initialise classes etc - _fnColumnOptions( oSettings, iCol, $(nTh).data() ); - } - - - /** - * Apply options for a column - * @param {object} oSettings dataTables settings object - * @param {int} iCol column index to consider - * @param {object} oOptions object with sType, bVisible and bSearchable etc - * @memberof DataTable#oApi - */ - function _fnColumnOptions( oSettings, iCol, oOptions ) - { - var oCol = oSettings.aoColumns[ iCol ]; - var oClasses = oSettings.oClasses; - var th = $(oCol.nTh); - - // Try to get width information from the DOM. We can't get it from CSS - // as we'd need to parse the CSS stylesheet. `width` option can override - if ( ! oCol.sWidthOrig ) { - // Width attribute - oCol.sWidthOrig = th.attr('width') || null; - - // Style attribute - var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); - if ( t ) { - oCol.sWidthOrig = t[1]; - } - } - - /* User specified column options */ - if ( oOptions !== undefined && oOptions !== null ) - { - // Backwards compatibility - _fnCompatCols( oOptions ); - - // Map camel case parameters to their Hungarian counterparts - _fnCamelToHungarian( DataTable.defaults.column, oOptions, true ); - - /* Backwards compatibility for mDataProp */ - if ( oOptions.mDataProp !== undefined && !oOptions.mData ) - { - oOptions.mData = oOptions.mDataProp; - } - - if ( oOptions.sType ) - { - oCol._sManualType = oOptions.sType; - } - - // `class` is a reserved word in Javascript, so we need to provide - // the ability to use a valid name for the camel case input - if ( oOptions.className && ! oOptions.sClass ) - { - oOptions.sClass = oOptions.className; - } - if ( oOptions.sClass ) { - th.addClass( oOptions.sClass ); - } - - $.extend( oCol, oOptions ); - _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); - - /* iDataSort to be applied (backwards compatibility), but aDataSort will take - * priority if defined - */ - if ( oOptions.iDataSort !== undefined ) - { - oCol.aDataSort = [ oOptions.iDataSort ]; - } - _fnMap( oCol, oOptions, "aDataSort" ); - } - - /* Cache the data get and set functions for speed */ - var mDataSrc = oCol.mData; - var mData = _fnGetObjectDataFn( mDataSrc ); - var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; - - var attrTest = function( src ) { - return typeof src === 'string' && src.indexOf('@') !== -1; - }; - oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( - attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) - ); - oCol._setter = null; - - oCol.fnGetData = function (rowData, type, meta) { - var innerData = mData( rowData, type, undefined, meta ); - - return mRender && type ? - mRender( innerData, type, rowData, meta ) : - innerData; - }; - oCol.fnSetData = function ( rowData, val, meta ) { - return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); - }; - - // Indicate if DataTables should read DOM data as an object or array - // Used in _fnGetRowElements - if ( typeof mDataSrc !== 'number' ) { - oSettings._rowReadObject = true; - } - - /* Feature sorting overrides column specific when off */ - if ( !oSettings.oFeatures.bSort ) - { - oCol.bSortable = false; - th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called - } - - /* Check that the class assignment is correct for sorting */ - var bAsc = $.inArray('asc', oCol.asSorting) !== -1; - var bDesc = $.inArray('desc', oCol.asSorting) !== -1; - if ( !oCol.bSortable || (!bAsc && !bDesc) ) - { - oCol.sSortingClass = oClasses.sSortableNone; - oCol.sSortingClassJUI = ""; - } - else if ( bAsc && !bDesc ) - { - oCol.sSortingClass = oClasses.sSortableAsc; - oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; - } - else if ( !bAsc && bDesc ) - { - oCol.sSortingClass = oClasses.sSortableDesc; - oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; - } - else - { - oCol.sSortingClass = oClasses.sSortable; - oCol.sSortingClassJUI = oClasses.sSortJUI; - } - } - - - /** - * Adjust the table column widths for new data. Note: you would probably want to - * do a redraw after calling this function! - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnAdjustColumnSizing ( settings ) - { - /* Not interested in doing column width calculation if auto-width is disabled */ - if ( settings.oFeatures.bAutoWidth !== false ) - { - var columns = settings.aoColumns; - - _fnCalculateColumnWidths( settings ); - for ( var i=0 , iLen=columns.length ; i<iLen ; i++ ) - { - columns[i].nTh.style.width = columns[i].sWidth; - } - } - - var scroll = settings.oScroll; - if ( scroll.sY !== '' || scroll.sX !== '') - { - _fnScrollDraw( settings ); - } - - _fnCallbackFire( settings, null, 'column-sizing', [settings] ); - } - - - /** - * Covert the index of a visible column to the index in the data array (take account - * of hidden columns) - * @param {object} oSettings dataTables settings object - * @param {int} iMatch Visible column index to lookup - * @returns {int} i the data index - * @memberof DataTable#oApi - */ - function _fnVisibleToColumnIndex( oSettings, iMatch ) - { - var aiVis = _fnGetColumns( oSettings, 'bVisible' ); - - return typeof aiVis[iMatch] === 'number' ? - aiVis[iMatch] : - null; - } - - - /** - * Covert the index of an index in the data array and convert it to the visible - * column index (take account of hidden columns) - * @param {int} iMatch Column index to lookup - * @param {object} oSettings dataTables settings object - * @returns {int} i the data index - * @memberof DataTable#oApi - */ - function _fnColumnIndexToVisible( oSettings, iMatch ) - { - var aiVis = _fnGetColumns( oSettings, 'bVisible' ); - var iPos = $.inArray( iMatch, aiVis ); - - return iPos !== -1 ? iPos : null; - } - - - /** - * Get the number of visible columns - * @param {object} oSettings dataTables settings object - * @returns {int} i the number of visible columns - * @memberof DataTable#oApi - */ - function _fnVisbleColumns( oSettings ) - { - var vis = 0; - - // No reduce in IE8, use a loop for now - $.each( oSettings.aoColumns, function ( i, col ) { - if ( col.bVisible && $(col.nTh).css('display') !== 'none' ) { - vis++; - } - } ); - - return vis; - } - - - /** - * Get an array of column indexes that match a given property - * @param {object} oSettings dataTables settings object - * @param {string} sParam Parameter in aoColumns to look for - typically - * bVisible or bSearchable - * @returns {array} Array of indexes with matched properties - * @memberof DataTable#oApi - */ - function _fnGetColumns( oSettings, sParam ) - { - var a = []; - - $.map( oSettings.aoColumns, function(val, i) { - if ( val[sParam] ) { - a.push( i ); - } - } ); - - return a; - } - - - /** - * Calculate the 'type' of a column - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnColumnTypes ( settings ) - { - var columns = settings.aoColumns; - var data = settings.aoData; - var types = DataTable.ext.type.detect; - var i, ien, j, jen, k, ken; - var col, cell, detectedType, cache; - - // For each column, spin over the - for ( i=0, ien=columns.length ; i<ien ; i++ ) { - col = columns[i]; - cache = []; - - if ( ! col.sType && col._sManualType ) { - col.sType = col._sManualType; - } - else if ( ! col.sType ) { - for ( j=0, jen=types.length ; j<jen ; j++ ) { - for ( k=0, ken=data.length ; k<ken ; k++ ) { - // Use a cache array so we only need to get the type data - // from the formatter once (when using multiple detectors) - if ( cache[k] === undefined ) { - cache[k] = _fnGetCellData( settings, k, i, 'type' ); - } - - detectedType = types[j]( cache[k], settings ); - - // If null, then this type can't apply to this column, so - // rather than testing all cells, break out. There is an - // exception for the last type which is `html`. We need to - // scan all rows since it is possible to mix string and HTML - // types - if ( ! detectedType && j !== types.length-1 ) { - break; - } - - // Only a single match is needed for html type since it is - // bottom of the pile and very similar to string - if ( detectedType === 'html' ) { - break; - } - } - - // Type is valid for all data points in the column - use this - // type - if ( detectedType ) { - col.sType = detectedType; - break; - } - } - - // Fall back - if no type was detected, always use string - if ( ! col.sType ) { - col.sType = 'string'; - } - } - } - } - - - /** - * Take the column definitions and static columns arrays and calculate how - * they relate to column indexes. The callback function will then apply the - * definition found for a column to a suitable configuration object. - * @param {object} oSettings dataTables settings object - * @param {array} aoColDefs The aoColumnDefs array that is to be applied - * @param {array} aoCols The aoColumns array that defines columns individually - * @param {function} fn Callback function - takes two parameters, the calculated - * column index and the definition for that column. - * @memberof DataTable#oApi - */ - function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn ) - { - var i, iLen, j, jLen, k, kLen, def; - var columns = oSettings.aoColumns; - - // Column definitions with aTargets - if ( aoColDefs ) - { - /* Loop over the definitions array - loop in reverse so first instance has priority */ - for ( i=aoColDefs.length-1 ; i>=0 ; i-- ) - { - def = aoColDefs[i]; - - /* Each definition can target multiple columns, as it is an array */ - var aTargets = def.targets !== undefined ? - def.targets : - def.aTargets; - - if ( ! Array.isArray( aTargets ) ) - { - aTargets = [ aTargets ]; - } - - for ( j=0, jLen=aTargets.length ; j<jLen ; j++ ) - { - if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 ) - { - /* Add columns that we don't yet know about */ - while( columns.length <= aTargets[j] ) - { - _fnAddColumn( oSettings ); - } - - /* Integer, basic index */ - fn( aTargets[j], def ); - } - else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 ) - { - /* Negative integer, right to left column counting */ - fn( columns.length+aTargets[j], def ); - } - else if ( typeof aTargets[j] === 'string' ) - { - /* Class name matching on TH element */ - for ( k=0, kLen=columns.length ; k<kLen ; k++ ) - { - if ( aTargets[j] == "_all" || - $(columns[k].nTh).hasClass( aTargets[j] ) ) - { - fn( k, def ); - } - } - } - } - } - } - - // Statically defined columns array - if ( aoCols ) - { - for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) - { - fn( i, aoCols[i] ); - } - } - } - - /** - * Add a data array to the table, creating DOM node etc. This is the parallel to - * _fnGatherData, but for adding rows from a Javascript source, rather than a - * DOM source. - * @param {object} oSettings dataTables settings object - * @param {array} aData data array to be added - * @param {node} [nTr] TR element to add to the table - optional. If not given, - * DataTables will create a row automatically - * @param {array} [anTds] Array of TD|TH elements for the row - must be given - * if nTr is. - * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed - * @memberof DataTable#oApi - */ - function _fnAddData ( oSettings, aDataIn, nTr, anTds ) - { - /* Create the object for storing information about this new row */ - var iRow = oSettings.aoData.length; - var oData = $.extend( true, {}, DataTable.models.oRow, { - src: nTr ? 'dom' : 'data', - idx: iRow - } ); - - oData._aData = aDataIn; - oSettings.aoData.push( oData ); - - /* Create the cells */ - var nTd, sThisType; - var columns = oSettings.aoColumns; - - // Invalidate the column types as the new data needs to be revalidated - for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) - { - columns[i].sType = null; - } - - /* Add to the display array */ - oSettings.aiDisplayMaster.push( iRow ); - - var id = oSettings.rowIdFn( aDataIn ); - if ( id !== undefined ) { - oSettings.aIds[ id ] = oData; - } - - /* Create the DOM information, or register it if already present */ - if ( nTr || ! oSettings.oFeatures.bDeferRender ) - { - _fnCreateTr( oSettings, iRow, nTr, anTds ); - } - - return iRow; - } - - - /** - * Add one or more TR elements to the table. Generally we'd expect to - * use this for reading data from a DOM sourced table, but it could be - * used for an TR element. Note that if a TR is given, it is used (i.e. - * it is not cloned). - * @param {object} settings dataTables settings object - * @param {array|node|jQuery} trs The TR element(s) to add to the table - * @returns {array} Array of indexes for the added rows - * @memberof DataTable#oApi - */ - function _fnAddTr( settings, trs ) - { - var row; - - // Allow an individual node to be passed in - if ( ! (trs instanceof $) ) { - trs = $(trs); - } - - return trs.map( function (i, el) { - row = _fnGetRowElements( settings, el ); - return _fnAddData( settings, row.data, el, row.cells ); - } ); - } - - - /** - * Take a TR element and convert it to an index in aoData - * @param {object} oSettings dataTables settings object - * @param {node} n the TR element to find - * @returns {int} index if the node is found, null if not - * @memberof DataTable#oApi - */ - function _fnNodeToDataIndex( oSettings, n ) - { - return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null; - } - - - /** - * Take a TD element and convert it into a column data index (not the visible index) - * @param {object} oSettings dataTables settings object - * @param {int} iRow The row number the TD/TH can be found in - * @param {node} n The TD/TH element to find - * @returns {int} index if the node is found, -1 if not - * @memberof DataTable#oApi - */ - function _fnNodeToColumnIndex( oSettings, iRow, n ) - { - return $.inArray( n, oSettings.aoData[ iRow ].anCells ); - } - - - /** - * Get the data for a given cell from the internal cache, taking into account data mapping - * @param {object} settings dataTables settings object - * @param {int} rowIdx aoData row id - * @param {int} colIdx Column index - * @param {string} type data get type ('display', 'type' 'filter' 'sort') - * @returns {*} Cell data - * @memberof DataTable#oApi - */ - function _fnGetCellData( settings, rowIdx, colIdx, type ) - { - var draw = settings.iDraw; - var col = settings.aoColumns[colIdx]; - var rowData = settings.aoData[rowIdx]._aData; - var defaultContent = col.sDefaultContent; - var cellData = col.fnGetData( rowData, type, { - settings: settings, - row: rowIdx, - col: colIdx - } ); - - if ( cellData === undefined ) { - if ( settings.iDrawError != draw && defaultContent === null ) { - _fnLog( settings, 0, "Requested unknown parameter "+ - (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+ - " for row "+rowIdx+", column "+colIdx, 4 ); - settings.iDrawError = draw; - } - return defaultContent; - } - - // When the data source is null and a specific data type is requested (i.e. - // not the original data), we can use default column data - if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) { - cellData = defaultContent; - } - else if ( typeof cellData === 'function' ) { - // If the data source is a function, then we run it and use the return, - // executing in the scope of the data object (for instances) - return cellData.call( rowData ); - } - - if ( cellData === null && type == 'display' ) { - return ''; - } - return cellData; - } - - - /** - * Set the value for a specific cell, into the internal data cache - * @param {object} settings dataTables settings object - * @param {int} rowIdx aoData row id - * @param {int} colIdx Column index - * @param {*} val Value to set - * @memberof DataTable#oApi - */ - function _fnSetCellData( settings, rowIdx, colIdx, val ) - { - var col = settings.aoColumns[colIdx]; - var rowData = settings.aoData[rowIdx]._aData; - - col.fnSetData( rowData, val, { - settings: settings, - row: rowIdx, - col: colIdx - } ); - } - - - // Private variable that is used to match action syntax in the data property object - var __reArray = /\[.*?\]$/; - var __reFn = /\(\)$/; - - /** - * Split string on periods, taking into account escaped periods - * @param {string} str String to split - * @return {array} Split string - */ - function _fnSplitObjNotation( str ) - { - return $.map( str.match(/(\\.|[^\.])+/g) || [''], function ( s ) { - return s.replace(/\\\./g, '.'); - } ); - } - - - /** - * Return a function that can be used to get data from a source object, taking - * into account the ability to use nested objects as a source - * @param {string|int|function} mSource The data source for the object - * @returns {function} Data get function - * @memberof DataTable#oApi - */ - function _fnGetObjectDataFn( mSource ) - { - if ( $.isPlainObject( mSource ) ) - { - /* Build an object of get functions, and wrap them in a single call */ - var o = {}; - $.each( mSource, function (key, val) { - if ( val ) { - o[key] = _fnGetObjectDataFn( val ); - } - } ); - - return function (data, type, row, meta) { - var t = o[type] || o._; - return t !== undefined ? - t(data, type, row, meta) : - data; - }; - } - else if ( mSource === null ) - { - /* Give an empty string for rendering / sorting etc */ - return function (data) { // type, row and meta also passed, but not used - return data; - }; - } - else if ( typeof mSource === 'function' ) - { - return function (data, type, row, meta) { - return mSource( data, type, row, meta ); - }; - } - else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || - mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) ) - { - /* If there is a . in the source string then the data source is in a - * nested object so we loop over the data for each level to get the next - * level down. On each loop we test for undefined, and if found immediately - * return. This allows entire objects to be missing and sDefaultContent to - * be used if defined, rather than throwing an error - */ - var fetchData = function (data, type, src) { - var arrayNotation, funcNotation, out, innerSrc; - - if ( src !== "" ) - { - var a = _fnSplitObjNotation( src ); - - for ( var i=0, iLen=a.length ; i<iLen ; i++ ) - { - // Check if we are dealing with special notation - arrayNotation = a[i].match(__reArray); - funcNotation = a[i].match(__reFn); - - if ( arrayNotation ) - { - // Array notation - a[i] = a[i].replace(__reArray, ''); - - // Condition allows simply [] to be passed in - if ( a[i] !== "" ) { - data = data[ a[i] ]; - } - out = []; - - // Get the remainder of the nested object to get - a.splice( 0, i+1 ); - innerSrc = a.join('.'); - - // Traverse each entry in the array getting the properties requested - if ( Array.isArray( data ) ) { - for ( var j=0, jLen=data.length ; j<jLen ; j++ ) { - out.push( fetchData( data[j], type, innerSrc ) ); - } - } - - // If a string is given in between the array notation indicators, that - // is used to join the strings together, otherwise an array is returned - var join = arrayNotation[0].substring(1, arrayNotation[0].length-1); - data = (join==="") ? out : out.join(join); - - // The inner call to fetchData has already traversed through the remainder - // of the source requested, so we exit from the loop - break; - } - else if ( funcNotation ) - { - // Function call - a[i] = a[i].replace(__reFn, ''); - data = data[ a[i] ](); - continue; - } - - if ( data === null || data[ a[i] ] === undefined ) - { - return undefined; - } - data = data[ a[i] ]; - } - } - - return data; - }; - - return function (data, type) { // row and meta also passed, but not used - return fetchData( data, type, mSource ); - }; - } - else - { - /* Array or flat object mapping */ - return function (data, type) { // row and meta also passed, but not used - return data[mSource]; - }; - } - } - - - /** - * Return a function that can be used to set data from a source object, taking - * into account the ability to use nested objects as a source - * @param {string|int|function} mSource The data source for the object - * @returns {function} Data set function - * @memberof DataTable#oApi - */ - function _fnSetObjectDataFn( mSource ) - { - if ( $.isPlainObject( mSource ) ) - { - /* Unlike get, only the underscore (global) option is used for for - * setting data since we don't know the type here. This is why an object - * option is not documented for `mData` (which is read/write), but it is - * for `mRender` which is read only. - */ - return _fnSetObjectDataFn( mSource._ ); - } - else if ( mSource === null ) - { - /* Nothing to do when the data source is null */ - return function () {}; - } - else if ( typeof mSource === 'function' ) - { - return function (data, val, meta) { - mSource( data, 'set', val, meta ); - }; - } - else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || - mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) ) - { - /* Like the get, we need to get data from a nested object */ - var setData = function (data, val, src) { - var a = _fnSplitObjNotation( src ), b; - var aLast = a[a.length-1]; - var arrayNotation, funcNotation, o, innerSrc; - - for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ ) - { - // Protect against prototype pollution - if (a[i] === '__proto__' || a[i] === 'constructor') { - throw new Error('Cannot set prototype values'); - } - - // Check if we are dealing with an array notation request - arrayNotation = a[i].match(__reArray); - funcNotation = a[i].match(__reFn); - - if ( arrayNotation ) - { - a[i] = a[i].replace(__reArray, ''); - data[ a[i] ] = []; - - // Get the remainder of the nested object to set so we can recurse - b = a.slice(); - b.splice( 0, i+1 ); - innerSrc = b.join('.'); - - // Traverse each entry in the array setting the properties requested - if ( Array.isArray( val ) ) - { - for ( var j=0, jLen=val.length ; j<jLen ; j++ ) - { - o = {}; - setData( o, val[j], innerSrc ); - data[ a[i] ].push( o ); - } - } - else - { - // We've been asked to save data to an array, but it - // isn't array data to be saved. Best that can be done - // is to just save the value. - data[ a[i] ] = val; - } - - // The inner call to setData has already traversed through the remainder - // of the source and has set the data, thus we can exit here - return; - } - else if ( funcNotation ) - { - // Function call - a[i] = a[i].replace(__reFn, ''); - data = data[ a[i] ]( val ); - } - - // If the nested object doesn't currently exist - since we are - // trying to set the value - create it - if ( data[ a[i] ] === null || data[ a[i] ] === undefined ) - { - data[ a[i] ] = {}; - } - data = data[ a[i] ]; - } - - // Last item in the input - i.e, the actual set - if ( aLast.match(__reFn ) ) - { - // Function call - data = data[ aLast.replace(__reFn, '') ]( val ); - } - else - { - // If array notation is used, we just want to strip it and use the property name - // and assign the value. If it isn't used, then we get the result we want anyway - data[ aLast.replace(__reArray, '') ] = val; - } - }; - - return function (data, val) { // meta is also passed in, but not used - return setData( data, val, mSource ); - }; - } - else - { - /* Array or flat object mapping */ - return function (data, val) { // meta is also passed in, but not used - data[mSource] = val; - }; - } - } - - - /** - * Return an array with the full table data - * @param {object} oSettings dataTables settings object - * @returns array {array} aData Master data array - * @memberof DataTable#oApi - */ - function _fnGetDataMaster ( settings ) - { - return _pluck( settings.aoData, '_aData' ); - } - - - /** - * Nuke the table - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnClearTable( settings ) - { - settings.aoData.length = 0; - settings.aiDisplayMaster.length = 0; - settings.aiDisplay.length = 0; - settings.aIds = {}; - } - - - /** - * Take an array of integers (index array) and remove a target integer (value - not - * the key!) - * @param {array} a Index array to target - * @param {int} iTarget value to find - * @memberof DataTable#oApi - */ - function _fnDeleteIndex( a, iTarget, splice ) - { - var iTargetIndex = -1; - - for ( var i=0, iLen=a.length ; i<iLen ; i++ ) - { - if ( a[i] == iTarget ) - { - iTargetIndex = i; - } - else if ( a[i] > iTarget ) - { - a[i]--; - } - } - - if ( iTargetIndex != -1 && splice === undefined ) - { - a.splice( iTargetIndex, 1 ); - } - } - - - /** - * Mark cached data as invalid such that a re-read of the data will occur when - * the cached data is next requested. Also update from the data source object. - * - * @param {object} settings DataTables settings object - * @param {int} rowIdx Row index to invalidate - * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom' - * or 'data' - * @param {int} [colIdx] Column index to invalidate. If undefined the whole - * row will be invalidated - * @memberof DataTable#oApi - * - * @todo For the modularisation of v1.11 this will need to become a callback, so - * the sort and filter methods can subscribe to it. That will required - * initialisation options for sorting, which is why it is not already baked in - */ - function _fnInvalidate( settings, rowIdx, src, colIdx ) - { - var row = settings.aoData[ rowIdx ]; - var i, ien; - var cellWrite = function ( cell, col ) { - // This is very frustrating, but in IE if you just write directly - // to innerHTML, and elements that are overwritten are GC'ed, - // even if there is a reference to them elsewhere - while ( cell.childNodes.length ) { - cell.removeChild( cell.firstChild ); - } - - cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' ); - }; - - // Are we reading last data from DOM or the data object? - if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) { - // Read the data from the DOM - row._aData = _fnGetRowElements( - settings, row, colIdx, colIdx === undefined ? undefined : row._aData - ) - .data; - } - else { - // Reading from data object, update the DOM - var cells = row.anCells; - - if ( cells ) { - if ( colIdx !== undefined ) { - cellWrite( cells[colIdx], colIdx ); - } - else { - for ( i=0, ien=cells.length ; i<ien ; i++ ) { - cellWrite( cells[i], i ); - } - } - } - } - - // For both row and cell invalidation, the cached data for sorting and - // filtering is nulled out - row._aSortData = null; - row._aFilterData = null; - - // Invalidate the type for a specific column (if given) or all columns since - // the data might have changed - var cols = settings.aoColumns; - if ( colIdx !== undefined ) { - cols[ colIdx ].sType = null; - } - else { - for ( i=0, ien=cols.length ; i<ien ; i++ ) { - cols[i].sType = null; - } - - // Update DataTables special `DT_*` attributes for the row - _fnRowAttributes( settings, row ); - } - } - - - /** - * Build a data source object from an HTML row, reading the contents of the - * cells that are in the row. - * - * @param {object} settings DataTables settings object - * @param {node|object} TR element from which to read data or existing row - * object from which to re-read the data from the cells - * @param {int} [colIdx] Optional column index - * @param {array|object} [d] Data source object. If `colIdx` is given then this - * parameter should also be given and will be used to write the data into. - * Only the column in question will be written - * @returns {object} Object with two parameters: `data` the data read, in - * document order, and `cells` and array of nodes (they can be useful to the - * caller, so rather than needing a second traversal to get them, just return - * them from here). - * @memberof DataTable#oApi - */ - function _fnGetRowElements( settings, row, colIdx, d ) - { - var - tds = [], - td = row.firstChild, - name, col, o, i=0, contents, - columns = settings.aoColumns, - objectRead = settings._rowReadObject; - - // Allow the data object to be passed in, or construct - d = d !== undefined ? - d : - objectRead ? - {} : - []; - - var attr = function ( str, td ) { - if ( typeof str === 'string' ) { - var idx = str.indexOf('@'); - - if ( idx !== -1 ) { - var attr = str.substring( idx+1 ); - var setter = _fnSetObjectDataFn( str ); - setter( d, td.getAttribute( attr ) ); - } - } - }; - - // Read data from a cell and store into the data object - var cellProcess = function ( cell ) { - if ( colIdx === undefined || colIdx === i ) { - col = columns[i]; - contents = (cell.innerHTML).trim(); - - if ( col && col._bAttrSrc ) { - var setter = _fnSetObjectDataFn( col.mData._ ); - setter( d, contents ); - - attr( col.mData.sort, cell ); - attr( col.mData.type, cell ); - attr( col.mData.filter, cell ); - } - else { - // Depending on the `data` option for the columns the data can - // be read to either an object or an array. - if ( objectRead ) { - if ( ! col._setter ) { - // Cache the setter function - col._setter = _fnSetObjectDataFn( col.mData ); - } - col._setter( d, contents ); - } - else { - d[i] = contents; - } - } - } - - i++; - }; - - if ( td ) { - // `tr` element was passed in - while ( td ) { - name = td.nodeName.toUpperCase(); - - if ( name == "TD" || name == "TH" ) { - cellProcess( td ); - tds.push( td ); - } - - td = td.nextSibling; - } - } - else { - // Existing row object passed in - tds = row.anCells; - - for ( var j=0, jen=tds.length ; j<jen ; j++ ) { - cellProcess( tds[j] ); - } - } - - // Read the ID from the DOM if present - var rowNode = row.firstChild ? row : row.nTr; - - if ( rowNode ) { - var id = rowNode.getAttribute( 'id' ); - - if ( id ) { - _fnSetObjectDataFn( settings.rowId )( d, id ); - } - } - - return { - data: d, - cells: tds - }; - } - /** - * Create a new TR element (and it's TD children) for a row - * @param {object} oSettings dataTables settings object - * @param {int} iRow Row to consider - * @param {node} [nTrIn] TR element to add to the table - optional. If not given, - * DataTables will create a row automatically - * @param {array} [anTds] Array of TD|TH elements for the row - must be given - * if nTr is. - * @memberof DataTable#oApi - */ - function _fnCreateTr ( oSettings, iRow, nTrIn, anTds ) - { - var - row = oSettings.aoData[iRow], - rowData = row._aData, - cells = [], - nTr, nTd, oCol, - i, iLen, create; - - if ( row.nTr === null ) - { - nTr = nTrIn || document.createElement('tr'); - - row.nTr = nTr; - row.anCells = cells; - - /* Use a private property on the node to allow reserve mapping from the node - * to the aoData array for fast look up - */ - nTr._DT_RowIndex = iRow; - - /* Special parameters can be given by the data source to be used on the row */ - _fnRowAttributes( oSettings, row ); - - /* Process each column */ - for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) - { - oCol = oSettings.aoColumns[i]; - create = nTrIn ? false : true; - - nTd = create ? document.createElement( oCol.sCellType ) : anTds[i]; - nTd._DT_CellIndex = { - row: iRow, - column: i - }; - - cells.push( nTd ); - - // Need to create the HTML if new, or if a rendering function is defined - if ( create || ((oCol.mRender || oCol.mData !== i) && - (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display') - )) { - nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' ); - } - - /* Add user defined class */ - if ( oCol.sClass ) - { - nTd.className += ' '+oCol.sClass; - } - - // Visibility - add or remove as required - if ( oCol.bVisible && ! nTrIn ) - { - nTr.appendChild( nTd ); - } - else if ( ! oCol.bVisible && nTrIn ) - { - nTd.parentNode.removeChild( nTd ); - } - - if ( oCol.fnCreatedCell ) - { - oCol.fnCreatedCell.call( oSettings.oInstance, - nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i - ); - } - } - - _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow, cells] ); - } - } - - - /** - * Add attributes to a row based on the special `DT_*` parameters in a data - * source object. - * @param {object} settings DataTables settings object - * @param {object} DataTables row object for the row to be modified - * @memberof DataTable#oApi - */ - function _fnRowAttributes( settings, row ) - { - var tr = row.nTr; - var data = row._aData; - - if ( tr ) { - var id = settings.rowIdFn( data ); - - if ( id ) { - tr.id = id; - } - - if ( data.DT_RowClass ) { - // Remove any classes added by DT_RowClass before - var a = data.DT_RowClass.split(' '); - row.__rowc = row.__rowc ? - _unique( row.__rowc.concat( a ) ) : - a; - - $(tr) - .removeClass( row.__rowc.join(' ') ) - .addClass( data.DT_RowClass ); - } - - if ( data.DT_RowAttr ) { - $(tr).attr( data.DT_RowAttr ); - } - - if ( data.DT_RowData ) { - $(tr).data( data.DT_RowData ); - } - } - } - - - /** - * Create the HTML header for the table - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnBuildHead( oSettings ) - { - var i, ien, cell, row, column; - var thead = oSettings.nTHead; - var tfoot = oSettings.nTFoot; - var createHeader = $('th, td', thead).length === 0; - var classes = oSettings.oClasses; - var columns = oSettings.aoColumns; - - if ( createHeader ) { - row = $('<tr/>').appendTo( thead ); - } - - for ( i=0, ien=columns.length ; i<ien ; i++ ) { - column = columns[i]; - cell = $( column.nTh ).addClass( column.sClass ); - - if ( createHeader ) { - cell.appendTo( row ); - } - - // 1.11 move into sorting - if ( oSettings.oFeatures.bSort ) { - cell.addClass( column.sSortingClass ); - - if ( column.bSortable !== false ) { - cell - .attr( 'tabindex', oSettings.iTabIndex ) - .attr( 'aria-controls', oSettings.sTableId ); - - _fnSortAttachListener( oSettings, column.nTh, i ); - } - } - - if ( column.sTitle != cell[0].innerHTML ) { - cell.html( column.sTitle ); - } - - _fnRenderer( oSettings, 'header' )( - oSettings, cell, column, classes - ); - } - - if ( createHeader ) { - _fnDetectHeader( oSettings.aoHeader, thead ); - } - - /* ARIA role for the rows */ - $(thead).children('tr').attr('role', 'row'); - - /* Deal with the footer - add classes if required */ - $(thead).children('tr').children('th, td').addClass( classes.sHeaderTH ); - $(tfoot).children('tr').children('th, td').addClass( classes.sFooterTH ); - - // Cache the footer cells. Note that we only take the cells from the first - // row in the footer. If there is more than one row the user wants to - // interact with, they need to use the table().foot() method. Note also this - // allows cells to be used for multiple columns using colspan - if ( tfoot !== null ) { - var cells = oSettings.aoFooter[0]; - - for ( i=0, ien=cells.length ; i<ien ; i++ ) { - column = columns[i]; - column.nTf = cells[i].cell; - - if ( column.sClass ) { - $(column.nTf).addClass( column.sClass ); - } - } - } - } - - - /** - * Draw the header (or footer) element based on the column visibility states. The - * methodology here is to use the layout array from _fnDetectHeader, modified for - * the instantaneous column visibility, to construct the new layout. The grid is - * traversed over cell at a time in a rows x columns grid fashion, although each - * cell insert can cover multiple elements in the grid - which is tracks using the - * aApplied array. Cell inserts in the grid will only occur where there isn't - * already a cell in that position. - * @param {object} oSettings dataTables settings object - * @param array {objects} aoSource Layout array from _fnDetectHeader - * @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc, - * @memberof DataTable#oApi - */ - function _fnDrawHead( oSettings, aoSource, bIncludeHidden ) - { - var i, iLen, j, jLen, k, kLen, n, nLocalTr; - var aoLocal = []; - var aApplied = []; - var iColumns = oSettings.aoColumns.length; - var iRowspan, iColspan; - - if ( ! aoSource ) - { - return; - } - - if ( bIncludeHidden === undefined ) - { - bIncludeHidden = false; - } - - /* Make a copy of the master layout array, but without the visible columns in it */ - for ( i=0, iLen=aoSource.length ; i<iLen ; i++ ) - { - aoLocal[i] = aoSource[i].slice(); - aoLocal[i].nTr = aoSource[i].nTr; - - /* Remove any columns which are currently hidden */ - for ( j=iColumns-1 ; j>=0 ; j-- ) - { - if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden ) - { - aoLocal[i].splice( j, 1 ); - } - } - - /* Prep the applied array - it needs an element for each row */ - aApplied.push( [] ); - } - - for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ ) - { - nLocalTr = aoLocal[i].nTr; - - /* All cells are going to be replaced, so empty out the row */ - if ( nLocalTr ) - { - while( (n = nLocalTr.firstChild) ) - { - nLocalTr.removeChild( n ); - } - } - - for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ ) - { - iRowspan = 1; - iColspan = 1; - - /* Check to see if there is already a cell (row/colspan) covering our target - * insert point. If there is, then there is nothing to do. - */ - if ( aApplied[i][j] === undefined ) - { - nLocalTr.appendChild( aoLocal[i][j].cell ); - aApplied[i][j] = 1; - - /* Expand the cell to cover as many rows as needed */ - while ( aoLocal[i+iRowspan] !== undefined && - aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell ) - { - aApplied[i+iRowspan][j] = 1; - iRowspan++; - } - - /* Expand the cell to cover as many columns as needed */ - while ( aoLocal[i][j+iColspan] !== undefined && - aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell ) - { - /* Must update the applied array over the rows for the columns */ - for ( k=0 ; k<iRowspan ; k++ ) - { - aApplied[i+k][j+iColspan] = 1; - } - iColspan++; - } - - /* Do the actual expansion in the DOM */ - $(aoLocal[i][j].cell) - .attr('rowspan', iRowspan) - .attr('colspan', iColspan); - } - } - } - } - - - /** - * Insert the required TR nodes into the table for display - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnDraw( oSettings ) - { - /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ - var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] ); - if ( $.inArray( false, aPreDraw ) !== -1 ) - { - _fnProcessingDisplay( oSettings, false ); - return; - } - - var i, iLen, n; - var anRows = []; - var iRowCount = 0; - var asStripeClasses = oSettings.asStripeClasses; - var iStripes = asStripeClasses.length; - var iOpenRows = oSettings.aoOpenRows.length; - var oLang = oSettings.oLanguage; - var iInitDisplayStart = oSettings.iInitDisplayStart; - var bServerSide = _fnDataSource( oSettings ) == 'ssp'; - var aiDisplay = oSettings.aiDisplay; - - oSettings.bDrawing = true; - - /* Check and see if we have an initial draw position from state saving */ - if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 ) - { - oSettings._iDisplayStart = bServerSide ? - iInitDisplayStart : - iInitDisplayStart >= oSettings.fnRecordsDisplay() ? - 0 : - iInitDisplayStart; - - oSettings.iInitDisplayStart = -1; - } - - var iDisplayStart = oSettings._iDisplayStart; - var iDisplayEnd = oSettings.fnDisplayEnd(); - - /* Server-side processing draw intercept */ - if ( oSettings.bDeferLoading ) - { - oSettings.bDeferLoading = false; - oSettings.iDraw++; - _fnProcessingDisplay( oSettings, false ); - } - else if ( !bServerSide ) - { - oSettings.iDraw++; - } - else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) ) - { - return; - } - - if ( aiDisplay.length !== 0 ) - { - var iStart = bServerSide ? 0 : iDisplayStart; - var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd; - - for ( var j=iStart ; j<iEnd ; j++ ) - { - var iDataIndex = aiDisplay[j]; - var aoData = oSettings.aoData[ iDataIndex ]; - if ( aoData.nTr === null ) - { - _fnCreateTr( oSettings, iDataIndex ); - } - - var nRow = aoData.nTr; - - /* Remove the old striping classes and then add the new one */ - if ( iStripes !== 0 ) - { - var sStripe = asStripeClasses[ iRowCount % iStripes ]; - if ( aoData._sRowStripe != sStripe ) - { - $(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe ); - aoData._sRowStripe = sStripe; - } - } - - // Row callback functions - might want to manipulate the row - // iRowCount and j are not currently documented. Are they at all - // useful? - _fnCallbackFire( oSettings, 'aoRowCallback', null, - [nRow, aoData._aData, iRowCount, j, iDataIndex] ); - - anRows.push( nRow ); - iRowCount++; - } - } - else - { - /* Table is empty - create a row with an empty message in it */ - var sZero = oLang.sZeroRecords; - if ( oSettings.iDraw == 1 && _fnDataSource( oSettings ) == 'ajax' ) - { - sZero = oLang.sLoadingRecords; - } - else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 ) - { - sZero = oLang.sEmptyTable; - } - - anRows[ 0 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } ) - .append( $('<td />', { - 'valign': 'top', - 'colSpan': _fnVisbleColumns( oSettings ), - 'class': oSettings.oClasses.sRowEmpty - } ).html( sZero ) )[0]; - } - - /* Header and footer callbacks */ - _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], - _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); - - _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], - _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); - - var body = $(oSettings.nTBody); - - body.children().detach(); - body.append( $(anRows) ); - - /* Call all required callback functions for the end of a draw */ - _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] ); - - /* Draw is complete, sorting and filtering must be as well */ - oSettings.bSorted = false; - oSettings.bFiltered = false; - oSettings.bDrawing = false; - } - - - /** - * Redraw the table - taking account of the various features which are enabled - * @param {object} oSettings dataTables settings object - * @param {boolean} [holdPosition] Keep the current paging position. By default - * the paging is reset to the first page - * @memberof DataTable#oApi - */ - function _fnReDraw( settings, holdPosition ) - { - var - features = settings.oFeatures, - sort = features.bSort, - filter = features.bFilter; - - if ( sort ) { - _fnSort( settings ); - } - - if ( filter ) { - _fnFilterComplete( settings, settings.oPreviousSearch ); - } - else { - // No filtering, so we want to just use the display master - settings.aiDisplay = settings.aiDisplayMaster.slice(); - } - - if ( holdPosition !== true ) { - settings._iDisplayStart = 0; - } - - // Let any modules know about the draw hold position state (used by - // scrolling internally) - settings._drawHold = holdPosition; - - _fnDraw( settings ); - - settings._drawHold = false; - } - - - /** - * Add the options to the page HTML for the table - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnAddOptionsHtml ( oSettings ) - { - var classes = oSettings.oClasses; - var table = $(oSettings.nTable); - var holding = $('<div/>').insertBefore( table ); // Holding element for speed - var features = oSettings.oFeatures; - - // All DataTables are wrapped in a div - var insert = $('<div/>', { - id: oSettings.sTableId+'_wrapper', - 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter) - } ); - - oSettings.nHolding = holding[0]; - oSettings.nTableWrapper = insert[0]; - oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; - - /* Loop over the user set positioning and place the elements as needed */ - var aDom = oSettings.sDom.split(''); - var featureNode, cOption, nNewNode, cNext, sAttr, j; - for ( var i=0 ; i<aDom.length ; i++ ) - { - featureNode = null; - cOption = aDom[i]; - - if ( cOption == '<' ) - { - /* New container div */ - nNewNode = $('<div/>')[0]; - - /* Check to see if we should append an id and/or a class name to the container */ - cNext = aDom[i+1]; - if ( cNext == "'" || cNext == '"' ) - { - sAttr = ""; - j = 2; - while ( aDom[i+j] != cNext ) - { - sAttr += aDom[i+j]; - j++; - } - - /* Replace jQuery UI constants @todo depreciated */ - if ( sAttr == "H" ) - { - sAttr = classes.sJUIHeader; - } - else if ( sAttr == "F" ) - { - sAttr = classes.sJUIFooter; - } - - /* The attribute can be in the format of "#id.class", "#id" or "class" This logic - * breaks the string into parts and applies them as needed - */ - if ( sAttr.indexOf('.') != -1 ) - { - var aSplit = sAttr.split('.'); - nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1); - nNewNode.className = aSplit[1]; - } - else if ( sAttr.charAt(0) == "#" ) - { - nNewNode.id = sAttr.substr(1, sAttr.length-1); - } - else - { - nNewNode.className = sAttr; - } - - i += j; /* Move along the position array */ - } - - insert.append( nNewNode ); - insert = $(nNewNode); - } - else if ( cOption == '>' ) - { - /* End container div */ - insert = insert.parent(); - } - // @todo Move options into their own plugins? - else if ( cOption == 'l' && features.bPaginate && features.bLengthChange ) - { - /* Length */ - featureNode = _fnFeatureHtmlLength( oSettings ); - } - else if ( cOption == 'f' && features.bFilter ) - { - /* Filter */ - featureNode = _fnFeatureHtmlFilter( oSettings ); - } - else if ( cOption == 'r' && features.bProcessing ) - { - /* pRocessing */ - featureNode = _fnFeatureHtmlProcessing( oSettings ); - } - else if ( cOption == 't' ) - { - /* Table */ - featureNode = _fnFeatureHtmlTable( oSettings ); - } - else if ( cOption == 'i' && features.bInfo ) - { - /* Info */ - featureNode = _fnFeatureHtmlInfo( oSettings ); - } - else if ( cOption == 'p' && features.bPaginate ) - { - /* Pagination */ - featureNode = _fnFeatureHtmlPaginate( oSettings ); - } - else if ( DataTable.ext.feature.length !== 0 ) - { - /* Plug-in features */ - var aoFeatures = DataTable.ext.feature; - for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ ) - { - if ( cOption == aoFeatures[k].cFeature ) - { - featureNode = aoFeatures[k].fnInit( oSettings ); - break; - } - } - } - - /* Add to the 2D features array */ - if ( featureNode ) - { - var aanFeatures = oSettings.aanFeatures; - - if ( ! aanFeatures[cOption] ) - { - aanFeatures[cOption] = []; - } - - aanFeatures[cOption].push( featureNode ); - insert.append( featureNode ); - } - } - - /* Built our DOM structure - replace the holding div with what we want */ - holding.replaceWith( insert ); - oSettings.nHolding = null; - } - - - /** - * Use the DOM source to create up an array of header cells. The idea here is to - * create a layout grid (array) of rows x columns, which contains a reference - * to the cell that that point in the grid (regardless of col/rowspan), such that - * any column / row could be removed and the new grid constructed - * @param array {object} aLayout Array to store the calculated layout in - * @param {node} nThead The header/footer element for the table - * @memberof DataTable#oApi - */ - function _fnDetectHeader ( aLayout, nThead ) - { - var nTrs = $(nThead).children('tr'); - var nTr, nCell; - var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan; - var bUnique; - var fnShiftCol = function ( a, i, j ) { - var k = a[i]; - while ( k[j] ) { - j++; - } - return j; - }; - - aLayout.splice( 0, aLayout.length ); - - /* We know how many rows there are in the layout - so prep it */ - for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) - { - aLayout.push( [] ); - } - - /* Calculate a layout array */ - for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) - { - nTr = nTrs[i]; - iColumn = 0; - - /* For every cell in the row... */ - nCell = nTr.firstChild; - while ( nCell ) { - if ( nCell.nodeName.toUpperCase() == "TD" || - nCell.nodeName.toUpperCase() == "TH" ) - { - /* Get the col and rowspan attributes from the DOM and sanitise them */ - iColspan = nCell.getAttribute('colspan') * 1; - iRowspan = nCell.getAttribute('rowspan') * 1; - iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan; - iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan; - - /* There might be colspan cells already in this row, so shift our target - * accordingly - */ - iColShifted = fnShiftCol( aLayout, i, iColumn ); - - /* Cache calculation for unique columns */ - bUnique = iColspan === 1 ? true : false; - - /* If there is col / rowspan, copy the information into the layout grid */ - for ( l=0 ; l<iColspan ; l++ ) - { - for ( k=0 ; k<iRowspan ; k++ ) - { - aLayout[i+k][iColShifted+l] = { - "cell": nCell, - "unique": bUnique - }; - aLayout[i+k].nTr = nTr; - } - } - } - nCell = nCell.nextSibling; - } - } - } - - - /** - * Get an array of unique th elements, one for each column - * @param {object} oSettings dataTables settings object - * @param {node} nHeader automatically detect the layout from this node - optional - * @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional - * @returns array {node} aReturn list of unique th's - * @memberof DataTable#oApi - */ - function _fnGetUniqueThs ( oSettings, nHeader, aLayout ) - { - var aReturn = []; - if ( !aLayout ) - { - aLayout = oSettings.aoHeader; - if ( nHeader ) - { - aLayout = []; - _fnDetectHeader( aLayout, nHeader ); - } - } - - for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ ) - { - for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ ) - { - if ( aLayout[i][j].unique && - (!aReturn[j] || !oSettings.bSortCellsTop) ) - { - aReturn[j] = aLayout[i][j].cell; - } - } - } - - return aReturn; - } - - /** - * Create an Ajax call based on the table's settings, taking into account that - * parameters can have multiple forms, and backwards compatibility. - * - * @param {object} oSettings dataTables settings object - * @param {array} data Data to send to the server, required by - * DataTables - may be augmented by developer callbacks - * @param {function} fn Callback function to run when data is obtained - */ - function _fnBuildAjax( oSettings, data, fn ) - { - // Compatibility with 1.9-, allow fnServerData and event to manipulate - _fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [data] ); - - // Convert to object based for 1.10+ if using the old array scheme which can - // come from server-side processing or serverParams - if ( data && Array.isArray(data) ) { - var tmp = {}; - var rbracket = /(.*?)\[\]$/; - - $.each( data, function (key, val) { - var match = val.name.match(rbracket); - - if ( match ) { - // Support for arrays - var name = match[0]; - - if ( ! tmp[ name ] ) { - tmp[ name ] = []; - } - tmp[ name ].push( val.value ); - } - else { - tmp[val.name] = val.value; - } - } ); - data = tmp; - } - - var ajaxData; - var ajax = oSettings.ajax; - var instance = oSettings.oInstance; - var callback = function ( json ) { - _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR] ); - fn( json ); - }; - - if ( $.isPlainObject( ajax ) && ajax.data ) - { - ajaxData = ajax.data; - - var newData = typeof ajaxData === 'function' ? - ajaxData( data, oSettings ) : // fn can manipulate data or return - ajaxData; // an object object or array to merge - - // If the function returned something, use that alone - data = typeof ajaxData === 'function' && newData ? - newData : - $.extend( true, data, newData ); - - // Remove the data property as we've resolved it already and don't want - // jQuery to do it again (it is restored at the end of the function) - delete ajax.data; - } - - var baseAjax = { - "data": data, - "success": function (json) { - var error = json.error || json.sError; - if ( error ) { - _fnLog( oSettings, 0, error ); - } - - oSettings.json = json; - callback( json ); - }, - "dataType": "json", - "cache": false, - "type": oSettings.sServerMethod, - "error": function (xhr, error, thrown) { - var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR] ); - - if ( $.inArray( true, ret ) === -1 ) { - if ( error == "parsererror" ) { - _fnLog( oSettings, 0, 'Invalid JSON response', 1 ); - } - else if ( xhr.readyState === 4 ) { - _fnLog( oSettings, 0, 'Ajax error', 7 ); - } - } - - _fnProcessingDisplay( oSettings, false ); - } - }; - - // Store the data submitted for the API - oSettings.oAjaxData = data; - - // Allow plug-ins and external processes to modify the data - _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data] ); - - if ( oSettings.fnServerData ) - { - // DataTables 1.9- compatibility - oSettings.fnServerData.call( instance, - oSettings.sAjaxSource, - $.map( data, function (val, key) { // Need to convert back to 1.9 trad format - return { name: key, value: val }; - } ), - callback, - oSettings - ); - } - else if ( oSettings.sAjaxSource || typeof ajax === 'string' ) - { - // DataTables 1.9- compatibility - oSettings.jqXHR = $.ajax( $.extend( baseAjax, { - url: ajax || oSettings.sAjaxSource - } ) ); - } - else if ( typeof ajax === 'function' ) - { - // Is a function - let the caller define what needs to be done - oSettings.jqXHR = ajax.call( instance, data, callback, oSettings ); - } - else - { - // Object to extend the base settings - oSettings.jqXHR = $.ajax( $.extend( baseAjax, ajax ) ); - - // Restore for next time around - ajax.data = ajaxData; - } - } - - - /** - * Update the table using an Ajax call - * @param {object} settings dataTables settings object - * @returns {boolean} Block the table drawing or not - * @memberof DataTable#oApi - */ - function _fnAjaxUpdate( settings ) - { - if ( settings.bAjaxDataGet ) { - settings.iDraw++; - _fnProcessingDisplay( settings, true ); - - _fnBuildAjax( - settings, - _fnAjaxParameters( settings ), - function(json) { - _fnAjaxUpdateDraw( settings, json ); - } - ); - - return false; - } - return true; - } - - - /** - * Build up the parameters in an object needed for a server-side processing - * request. Note that this is basically done twice, is different ways - a modern - * method which is used by default in DataTables 1.10 which uses objects and - * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if - * the sAjaxSource option is used in the initialisation, or the legacyAjax - * option is set. - * @param {object} oSettings dataTables settings object - * @returns {bool} block the table drawing or not - * @memberof DataTable#oApi - */ - function _fnAjaxParameters( settings ) - { - var - columns = settings.aoColumns, - columnCount = columns.length, - features = settings.oFeatures, - preSearch = settings.oPreviousSearch, - preColSearch = settings.aoPreSearchCols, - i, data = [], dataProp, column, columnSearch, - sort = _fnSortFlatten( settings ), - displayStart = settings._iDisplayStart, - displayLength = features.bPaginate !== false ? - settings._iDisplayLength : - -1; - - var param = function ( name, value ) { - data.push( { 'name': name, 'value': value } ); - }; - - // DataTables 1.9- compatible method - param( 'sEcho', settings.iDraw ); - param( 'iColumns', columnCount ); - param( 'sColumns', _pluck( columns, 'sName' ).join(',') ); - param( 'iDisplayStart', displayStart ); - param( 'iDisplayLength', displayLength ); - - // DataTables 1.10+ method - var d = { - draw: settings.iDraw, - columns: [], - order: [], - start: displayStart, - length: displayLength, - search: { - value: preSearch.sSearch, - regex: preSearch.bRegex - } - }; - - for ( i=0 ; i<columnCount ; i++ ) { - column = columns[i]; - columnSearch = preColSearch[i]; - dataProp = typeof column.mData=="function" ? 'function' : column.mData ; - - d.columns.push( { - data: dataProp, - name: column.sName, - searchable: column.bSearchable, - orderable: column.bSortable, - search: { - value: columnSearch.sSearch, - regex: columnSearch.bRegex - } - } ); - - param( "mDataProp_"+i, dataProp ); - - if ( features.bFilter ) { - param( 'sSearch_'+i, columnSearch.sSearch ); - param( 'bRegex_'+i, columnSearch.bRegex ); - param( 'bSearchable_'+i, column.bSearchable ); - } - - if ( features.bSort ) { - param( 'bSortable_'+i, column.bSortable ); - } - } - - if ( features.bFilter ) { - param( 'sSearch', preSearch.sSearch ); - param( 'bRegex', preSearch.bRegex ); - } - - if ( features.bSort ) { - $.each( sort, function ( i, val ) { - d.order.push( { column: val.col, dir: val.dir } ); - - param( 'iSortCol_'+i, val.col ); - param( 'sSortDir_'+i, val.dir ); - } ); - - param( 'iSortingCols', sort.length ); - } - - // If the legacy.ajax parameter is null, then we automatically decide which - // form to use, based on sAjaxSource - var legacy = DataTable.ext.legacy.ajax; - if ( legacy === null ) { - return settings.sAjaxSource ? data : d; - } - - // Otherwise, if legacy has been specified then we use that to decide on the - // form - return legacy ? data : d; - } - - - /** - * Data the data from the server (nuking the old) and redraw the table - * @param {object} oSettings dataTables settings object - * @param {object} json json data return from the server. - * @param {string} json.sEcho Tracking flag for DataTables to match requests - * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering - * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering - * @param {array} json.aaData The data to display on this page - * @param {string} [json.sColumns] Column ordering (sName, comma separated) - * @memberof DataTable#oApi - */ - function _fnAjaxUpdateDraw ( settings, json ) - { - // v1.10 uses camelCase variables, while 1.9 uses Hungarian notation. - // Support both - var compat = function ( old, modern ) { - return json[old] !== undefined ? json[old] : json[modern]; - }; - - var data = _fnAjaxDataSrc( settings, json ); - var draw = compat( 'sEcho', 'draw' ); - var recordsTotal = compat( 'iTotalRecords', 'recordsTotal' ); - var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' ); - - if ( draw !== undefined ) { - // Protect against out of sequence returns - if ( draw*1 < settings.iDraw ) { - return; - } - settings.iDraw = draw * 1; - } - - _fnClearTable( settings ); - settings._iRecordsTotal = parseInt(recordsTotal, 10); - settings._iRecordsDisplay = parseInt(recordsFiltered, 10); - - for ( var i=0, ien=data.length ; i<ien ; i++ ) { - _fnAddData( settings, data[i] ); - } - settings.aiDisplay = settings.aiDisplayMaster.slice(); - - settings.bAjaxDataGet = false; - _fnDraw( settings ); - - if ( ! settings._bInitComplete ) { - _fnInitComplete( settings, json ); - } - - settings.bAjaxDataGet = true; - _fnProcessingDisplay( settings, false ); - } - - - /** - * Get the data from the JSON data source to use for drawing a table. Using - * `_fnGetObjectDataFn` allows the data to be sourced from a property of the - * source object, or from a processing function. - * @param {object} oSettings dataTables settings object - * @param {object} json Data source object / array from the server - * @return {array} Array of data to use - */ - function _fnAjaxDataSrc ( oSettings, json ) - { - var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ? - oSettings.ajax.dataSrc : - oSettings.sAjaxDataProp; // Compatibility with 1.9-. - - // Compatibility with 1.9-. In order to read from aaData, check if the - // default has been changed, if not, check for aaData - if ( dataSrc === 'data' ) { - return json.aaData || json[dataSrc]; - } - - return dataSrc !== "" ? - _fnGetObjectDataFn( dataSrc )( json ) : - json; - } - - /** - * Generate the node required for filtering text - * @returns {node} Filter control element - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnFeatureHtmlFilter ( settings ) - { - var classes = settings.oClasses; - var tableId = settings.sTableId; - var language = settings.oLanguage; - var previousSearch = settings.oPreviousSearch; - var features = settings.aanFeatures; - var input = '<input type="search" class="'+classes.sFilterInput+'"/>'; - - var str = language.sSearch; - str = str.match(/_INPUT_/) ? - str.replace('_INPUT_', input) : - str+input; - - var filter = $('<div/>', { - 'id': ! features.f ? tableId+'_filter' : null, - 'class': classes.sFilter - } ) - .append( $('<label/>' ).append( str ) ); - - var searchFn = function() { - /* Update all other filter input elements for the new display */ - var n = features.f; - var val = !this.value ? "" : this.value; // mental IE8 fix :-( - - /* Now do the filter */ - if ( val != previousSearch.sSearch ) { - _fnFilterComplete( settings, { - "sSearch": val, - "bRegex": previousSearch.bRegex, - "bSmart": previousSearch.bSmart , - "bCaseInsensitive": previousSearch.bCaseInsensitive - } ); - - // Need to redraw, without resorting - settings._iDisplayStart = 0; - _fnDraw( settings ); - } - }; - - var searchDelay = settings.searchDelay !== null ? - settings.searchDelay : - _fnDataSource( settings ) === 'ssp' ? - 400 : - 0; - - var jqFilter = $('input', filter) - .val( previousSearch.sSearch ) - .attr( 'placeholder', language.sSearchPlaceholder ) - .on( - 'keyup.DT search.DT input.DT paste.DT cut.DT', - searchDelay ? - _fnThrottle( searchFn, searchDelay ) : - searchFn - ) - .on( 'mouseup', function(e) { - // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking - // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn` - // checks the value to see if it has changed. In other browsers it won't have. - setTimeout( function () { - searchFn.call(jqFilter[0]); - }, 10); - } ) - .on( 'keypress.DT', function(e) { - /* Prevent form submission */ - if ( e.keyCode == 13 ) { - return false; - } - } ) - .attr('aria-controls', tableId); - - // Update the input elements whenever the table is filtered - $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) { - if ( settings === s ) { - // IE9 throws an 'unknown error' if document.activeElement is used - // inside an iframe or frame... - try { - if ( jqFilter[0] !== document.activeElement ) { - jqFilter.val( previousSearch.sSearch ); - } - } - catch ( e ) {} - } - } ); - - return filter[0]; - } - - - /** - * Filter the table using both the global filter and column based filtering - * @param {object} oSettings dataTables settings object - * @param {object} oSearch search information - * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0) - * @memberof DataTable#oApi - */ - function _fnFilterComplete ( oSettings, oInput, iForce ) - { - var oPrevSearch = oSettings.oPreviousSearch; - var aoPrevSearch = oSettings.aoPreSearchCols; - var fnSaveFilter = function ( oFilter ) { - /* Save the filtering values */ - oPrevSearch.sSearch = oFilter.sSearch; - oPrevSearch.bRegex = oFilter.bRegex; - oPrevSearch.bSmart = oFilter.bSmart; - oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive; - }; - var fnRegex = function ( o ) { - // Backwards compatibility with the bEscapeRegex option - return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex; - }; - - // Resolve any column types that are unknown due to addition or invalidation - // @todo As per sort - can this be moved into an event handler? - _fnColumnTypes( oSettings ); - - /* In server-side processing all filtering is done by the server, so no point hanging around here */ - if ( _fnDataSource( oSettings ) != 'ssp' ) - { - /* Global filter */ - _fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive ); - fnSaveFilter( oInput ); - - /* Now do the individual column filter */ - for ( var i=0 ; i<aoPrevSearch.length ; i++ ) - { - _fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]), - aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive ); - } - - /* Custom filtering */ - _fnFilterCustom( oSettings ); - } - else - { - fnSaveFilter( oInput ); - } - - /* Tell the draw function we have been filtering */ - oSettings.bFiltered = true; - _fnCallbackFire( oSettings, null, 'search', [oSettings] ); - } - - - /** - * Apply custom filtering functions - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnFilterCustom( settings ) - { - var filters = DataTable.ext.search; - var displayRows = settings.aiDisplay; - var row, rowIdx; - - for ( var i=0, ien=filters.length ; i<ien ; i++ ) { - var rows = []; - - // Loop over each row and see if it should be included - for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) { - rowIdx = displayRows[ j ]; - row = settings.aoData[ rowIdx ]; - - if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) { - rows.push( rowIdx ); - } - } - - // So the array reference doesn't break set the results into the - // existing array - displayRows.length = 0; - $.merge( displayRows, rows ); - } - } - - - /** - * Filter the table on a per-column basis - * @param {object} oSettings dataTables settings object - * @param {string} sInput string to filter on - * @param {int} iColumn column to filter - * @param {bool} bRegex treat search string as a regular expression or not - * @param {bool} bSmart use smart filtering or not - * @param {bool} bCaseInsensitive Do case insenstive matching or not - * @memberof DataTable#oApi - */ - function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive ) - { - if ( searchStr === '' ) { - return; - } - - var data; - var out = []; - var display = settings.aiDisplay; - var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive ); - - for ( var i=0 ; i<display.length ; i++ ) { - data = settings.aoData[ display[i] ]._aFilterData[ colIdx ]; - - if ( rpSearch.test( data ) ) { - out.push( display[i] ); - } - } - - settings.aiDisplay = out; - } - - - /** - * Filter the data table based on user input and draw the table - * @param {object} settings dataTables settings object - * @param {string} input string to filter on - * @param {int} force optional - force a research of the master array (1) or not (undefined or 0) - * @param {bool} regex treat as a regular expression or not - * @param {bool} smart perform smart filtering or not - * @param {bool} caseInsensitive Do case insenstive matching or not - * @memberof DataTable#oApi - */ - function _fnFilter( settings, input, force, regex, smart, caseInsensitive ) - { - var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive ); - var prevSearch = settings.oPreviousSearch.sSearch; - var displayMaster = settings.aiDisplayMaster; - var display, invalidated, i; - var filtered = []; - - // Need to take account of custom filtering functions - always filter - if ( DataTable.ext.search.length !== 0 ) { - force = true; - } - - // Check if any of the rows were invalidated - invalidated = _fnFilterData( settings ); - - // If the input is blank - we just want the full data set - if ( input.length <= 0 ) { - settings.aiDisplay = displayMaster.slice(); - } - else { - // New search - start from the master array - if ( invalidated || - force || - regex || - prevSearch.length > input.length || - input.indexOf(prevSearch) !== 0 || - settings.bSorted // On resort, the display master needs to be - // re-filtered since indexes will have changed - ) { - settings.aiDisplay = displayMaster.slice(); - } - - // Search the display array - display = settings.aiDisplay; - - for ( i=0 ; i<display.length ; i++ ) { - if ( rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) { - filtered.push( display[i] ); - } - } - - settings.aiDisplay = filtered; - } - } - - - /** - * Build a regular expression object suitable for searching a table - * @param {string} sSearch string to search for - * @param {bool} bRegex treat as a regular expression or not - * @param {bool} bSmart perform smart filtering or not - * @param {bool} bCaseInsensitive Do case insensitive matching or not - * @returns {RegExp} constructed object - * @memberof DataTable#oApi - */ - function _fnFilterCreateSearch( search, regex, smart, caseInsensitive ) - { - search = regex ? - search : - _fnEscapeRegex( search ); - - if ( smart ) { - /* For smart filtering we want to allow the search to work regardless of - * word order. We also want double quoted text to be preserved, so word - * order is important - a la google. So this is what we want to - * generate: - * - * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ - */ - var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) { - if ( word.charAt(0) === '"' ) { - var m = word.match( /^"(.*)"$/ ); - word = m ? m[1] : word; - } - - return word.replace('"', ''); - } ); - - search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$'; - } - - return new RegExp( search, caseInsensitive ? 'i' : '' ); - } - - - /** - * Escape a string such that it can be used in a regular expression - * @param {string} sVal string to escape - * @returns {string} escaped string - * @memberof DataTable#oApi - */ - var _fnEscapeRegex = DataTable.util.escapeRegex; - - var __filter_div = $('<div>')[0]; - var __filter_div_textContent = __filter_div.textContent !== undefined; - - // Update the filtering data for each row if needed (by invalidation or first run) - function _fnFilterData ( settings ) - { - var columns = settings.aoColumns; - var column; - var i, j, ien, jen, filterData, cellData, row; - var fomatters = DataTable.ext.type.search; - var wasInvalidated = false; - - for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { - row = settings.aoData[i]; - - if ( ! row._aFilterData ) { - filterData = []; - - for ( j=0, jen=columns.length ; j<jen ; j++ ) { - column = columns[j]; - - if ( column.bSearchable ) { - cellData = _fnGetCellData( settings, i, j, 'filter' ); - - if ( fomatters[ column.sType ] ) { - cellData = fomatters[ column.sType ]( cellData ); - } - - // Search in DataTables 1.10 is string based. In 1.11 this - // should be altered to also allow strict type checking. - if ( cellData === null ) { - cellData = ''; - } - - if ( typeof cellData !== 'string' && cellData.toString ) { - cellData = cellData.toString(); - } - } - else { - cellData = ''; - } - - // If it looks like there is an HTML entity in the string, - // attempt to decode it so sorting works as expected. Note that - // we could use a single line of jQuery to do this, but the DOM - // method used here is much faster http://jsperf.com/html-decode - if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) { - __filter_div.innerHTML = cellData; - cellData = __filter_div_textContent ? - __filter_div.textContent : - __filter_div.innerText; - } - - if ( cellData.replace ) { - cellData = cellData.replace(/[\r\n\u2028]/g, ''); - } - - filterData.push( cellData ); - } - - row._aFilterData = filterData; - row._sFilterRow = filterData.join(' '); - wasInvalidated = true; - } - } - - return wasInvalidated; - } - - - /** - * Convert from the internal Hungarian notation to camelCase for external - * interaction - * @param {object} obj Object to convert - * @returns {object} Inverted object - * @memberof DataTable#oApi - */ - function _fnSearchToCamel ( obj ) - { - return { - search: obj.sSearch, - smart: obj.bSmart, - regex: obj.bRegex, - caseInsensitive: obj.bCaseInsensitive - }; - } - - - - /** - * Convert from camelCase notation to the internal Hungarian. We could use the - * Hungarian convert function here, but this is cleaner - * @param {object} obj Object to convert - * @returns {object} Inverted object - * @memberof DataTable#oApi - */ - function _fnSearchToHung ( obj ) - { - return { - sSearch: obj.search, - bSmart: obj.smart, - bRegex: obj.regex, - bCaseInsensitive: obj.caseInsensitive - }; - } - - /** - * Generate the node required for the info display - * @param {object} oSettings dataTables settings object - * @returns {node} Information element - * @memberof DataTable#oApi - */ - function _fnFeatureHtmlInfo ( settings ) - { - var - tid = settings.sTableId, - nodes = settings.aanFeatures.i, - n = $('<div/>', { - 'class': settings.oClasses.sInfo, - 'id': ! nodes ? tid+'_info' : null - } ); - - if ( ! nodes ) { - // Update display on each draw - settings.aoDrawCallback.push( { - "fn": _fnUpdateInfo, - "sName": "information" - } ); - - n - .attr( 'role', 'status' ) - .attr( 'aria-live', 'polite' ); - - // Table is described by our info div - $(settings.nTable).attr( 'aria-describedby', tid+'_info' ); - } - - return n[0]; - } - - - /** - * Update the information elements in the display - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnUpdateInfo ( settings ) - { - /* Show information about the table */ - var nodes = settings.aanFeatures.i; - if ( nodes.length === 0 ) { - return; - } - - var - lang = settings.oLanguage, - start = settings._iDisplayStart+1, - end = settings.fnDisplayEnd(), - max = settings.fnRecordsTotal(), - total = settings.fnRecordsDisplay(), - out = total ? - lang.sInfo : - lang.sInfoEmpty; - - if ( total !== max ) { - /* Record set after filtering */ - out += ' ' + lang.sInfoFiltered; - } - - // Convert the macros - out += lang.sInfoPostFix; - out = _fnInfoMacros( settings, out ); - - var callback = lang.fnInfoCallback; - if ( callback !== null ) { - out = callback.call( settings.oInstance, - settings, start, end, max, total, out - ); - } - - $(nodes).html( out ); - } - - - function _fnInfoMacros ( settings, str ) - { - // When infinite scrolling, we are always starting at 1. _iDisplayStart is used only - // internally - var - formatter = settings.fnFormatNumber, - start = settings._iDisplayStart+1, - len = settings._iDisplayLength, - vis = settings.fnRecordsDisplay(), - all = len === -1; - - return str. - replace(/_START_/g, formatter.call( settings, start ) ). - replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ). - replace(/_MAX_/g, formatter.call( settings, settings.fnRecordsTotal() ) ). - replace(/_TOTAL_/g, formatter.call( settings, vis ) ). - replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ). - replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ); - } - - - - /** - * Draw the table for the first time, adding all required features - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnInitialise ( settings ) - { - var i, iLen, iAjaxStart=settings.iInitDisplayStart; - var columns = settings.aoColumns, column; - var features = settings.oFeatures; - var deferLoading = settings.bDeferLoading; // value modified by the draw - - /* Ensure that the table data is fully initialised */ - if ( ! settings.bInitialised ) { - setTimeout( function(){ _fnInitialise( settings ); }, 200 ); - return; - } - - /* Show the display HTML options */ - _fnAddOptionsHtml( settings ); - - /* Build and draw the header / footer for the table */ - _fnBuildHead( settings ); - _fnDrawHead( settings, settings.aoHeader ); - _fnDrawHead( settings, settings.aoFooter ); - - /* Okay to show that something is going on now */ - _fnProcessingDisplay( settings, true ); - - /* Calculate sizes for columns */ - if ( features.bAutoWidth ) { - _fnCalculateColumnWidths( settings ); - } - - for ( i=0, iLen=columns.length ; i<iLen ; i++ ) { - column = columns[i]; - - if ( column.sWidth ) { - column.nTh.style.width = _fnStringToCss( column.sWidth ); - } - } - - _fnCallbackFire( settings, null, 'preInit', [settings] ); - - // If there is default sorting required - let's do it. The sort function - // will do the drawing for us. Otherwise we draw the table regardless of the - // Ajax source - this allows the table to look initialised for Ajax sourcing - // data (show 'loading' message possibly) - _fnReDraw( settings ); - - // Server-side processing init complete is done by _fnAjaxUpdateDraw - var dataSrc = _fnDataSource( settings ); - if ( dataSrc != 'ssp' || deferLoading ) { - // if there is an ajax source load the data - if ( dataSrc == 'ajax' ) { - _fnBuildAjax( settings, [], function(json) { - var aData = _fnAjaxDataSrc( settings, json ); - - // Got the data - add it to the table - for ( i=0 ; i<aData.length ; i++ ) { - _fnAddData( settings, aData[i] ); - } - - // Reset the init display for cookie saving. We've already done - // a filter, and therefore cleared it before. So we need to make - // it appear 'fresh' - settings.iInitDisplayStart = iAjaxStart; - - _fnReDraw( settings ); - - _fnProcessingDisplay( settings, false ); - _fnInitComplete( settings, json ); - }, settings ); - } - else { - _fnProcessingDisplay( settings, false ); - _fnInitComplete( settings ); - } - } - } - - - /** - * Draw the table for the first time, adding all required features - * @param {object} oSettings dataTables settings object - * @param {object} [json] JSON from the server that completed the table, if using Ajax source - * with client-side processing (optional) - * @memberof DataTable#oApi - */ - function _fnInitComplete ( settings, json ) - { - settings._bInitComplete = true; - - // When data was added after the initialisation (data or Ajax) we need to - // calculate the column sizing - if ( json || settings.oInit.aaData ) { - _fnAdjustColumnSizing( settings ); - } - - _fnCallbackFire( settings, null, 'plugin-init', [settings, json] ); - _fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] ); - } - - - function _fnLengthChange ( settings, val ) - { - var len = parseInt( val, 10 ); - settings._iDisplayLength = len; - - _fnLengthOverflow( settings ); - - // Fire length change event - _fnCallbackFire( settings, null, 'length', [settings, len] ); - } - - - /** - * Generate the node required for user display length changing - * @param {object} settings dataTables settings object - * @returns {node} Display length feature node - * @memberof DataTable#oApi - */ - function _fnFeatureHtmlLength ( settings ) - { - var - classes = settings.oClasses, - tableId = settings.sTableId, - menu = settings.aLengthMenu, - d2 = Array.isArray( menu[0] ), - lengths = d2 ? menu[0] : menu, - language = d2 ? menu[1] : menu; - - var select = $('<select/>', { - 'name': tableId+'_length', - 'aria-controls': tableId, - 'class': classes.sLengthSelect - } ); - - for ( var i=0, ien=lengths.length ; i<ien ; i++ ) { - select[0][ i ] = new Option( - typeof language[i] === 'number' ? - settings.fnFormatNumber( language[i] ) : - language[i], - lengths[i] - ); - } - - var div = $('<div><label/></div>').addClass( classes.sLength ); - if ( ! settings.aanFeatures.l ) { - div[0].id = tableId+'_length'; - } - - div.children().append( - settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML ) - ); - - // Can't use `select` variable as user might provide their own and the - // reference is broken by the use of outerHTML - $('select', div) - .val( settings._iDisplayLength ) - .on( 'change.DT', function(e) { - _fnLengthChange( settings, $(this).val() ); - _fnDraw( settings ); - } ); - - // Update node value whenever anything changes the table's length - $(settings.nTable).on( 'length.dt.DT', function (e, s, len) { - if ( settings === s ) { - $('select', div).val( len ); - } - } ); - - return div[0]; - } - - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Note that most of the paging logic is done in - * DataTable.ext.pager - */ - - /** - * Generate the node required for default pagination - * @param {object} oSettings dataTables settings object - * @returns {node} Pagination feature node - * @memberof DataTable#oApi - */ - function _fnFeatureHtmlPaginate ( settings ) - { - var - type = settings.sPaginationType, - plugin = DataTable.ext.pager[ type ], - modern = typeof plugin === 'function', - redraw = function( settings ) { - _fnDraw( settings ); - }, - node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0], - features = settings.aanFeatures; - - if ( ! modern ) { - plugin.fnInit( settings, node, redraw ); - } - - /* Add a draw callback for the pagination on first instance, to update the paging display */ - if ( ! features.p ) - { - node.id = settings.sTableId+'_paginate'; - - settings.aoDrawCallback.push( { - "fn": function( settings ) { - if ( modern ) { - var - start = settings._iDisplayStart, - len = settings._iDisplayLength, - visRecords = settings.fnRecordsDisplay(), - all = len === -1, - page = all ? 0 : Math.ceil( start / len ), - pages = all ? 1 : Math.ceil( visRecords / len ), - buttons = plugin(page, pages), - i, ien; - - for ( i=0, ien=features.p.length ; i<ien ; i++ ) { - _fnRenderer( settings, 'pageButton' )( - settings, features.p[i], i, buttons, page, pages - ); - } - } - else { - plugin.fnUpdate( settings, redraw ); - } - }, - "sName": "pagination" - } ); - } - - return node; - } - - - /** - * Alter the display settings to change the page - * @param {object} settings DataTables settings object - * @param {string|int} action Paging action to take: "first", "previous", - * "next" or "last" or page number to jump to (integer) - * @param [bool] redraw Automatically draw the update or not - * @returns {bool} true page has changed, false - no change - * @memberof DataTable#oApi - */ - function _fnPageChange ( settings, action, redraw ) - { - var - start = settings._iDisplayStart, - len = settings._iDisplayLength, - records = settings.fnRecordsDisplay(); - - if ( records === 0 || len === -1 ) - { - start = 0; - } - else if ( typeof action === "number" ) - { - start = action * len; - - if ( start > records ) - { - start = 0; - } - } - else if ( action == "first" ) - { - start = 0; - } - else if ( action == "previous" ) - { - start = len >= 0 ? - start - len : - 0; - - if ( start < 0 ) - { - start = 0; - } - } - else if ( action == "next" ) - { - if ( start + len < records ) - { - start += len; - } - } - else if ( action == "last" ) - { - start = Math.floor( (records-1) / len) * len; - } - else - { - _fnLog( settings, 0, "Unknown paging action: "+action, 5 ); - } - - var changed = settings._iDisplayStart !== start; - settings._iDisplayStart = start; - - if ( changed ) { - _fnCallbackFire( settings, null, 'page', [settings] ); - - if ( redraw ) { - _fnDraw( settings ); - } - } - - return changed; - } - - - - /** - * Generate the node required for the processing node - * @param {object} settings dataTables settings object - * @returns {node} Processing element - * @memberof DataTable#oApi - */ - function _fnFeatureHtmlProcessing ( settings ) - { - return $('<div/>', { - 'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null, - 'class': settings.oClasses.sProcessing - } ) - .html( settings.oLanguage.sProcessing ) - .insertBefore( settings.nTable )[0]; - } - - - /** - * Display or hide the processing indicator - * @param {object} settings dataTables settings object - * @param {bool} show Show the processing indicator (true) or not (false) - * @memberof DataTable#oApi - */ - function _fnProcessingDisplay ( settings, show ) - { - if ( settings.oFeatures.bProcessing ) { - $(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' ); - } - - _fnCallbackFire( settings, null, 'processing', [settings, show] ); - } - - /** - * Add any control elements for the table - specifically scrolling - * @param {object} settings dataTables settings object - * @returns {node} Node to add to the DOM - * @memberof DataTable#oApi - */ - function _fnFeatureHtmlTable ( settings ) - { - var table = $(settings.nTable); - - // Add the ARIA grid role to the table - table.attr( 'role', 'grid' ); - - // Scrolling from here on in - var scroll = settings.oScroll; - - if ( scroll.sX === '' && scroll.sY === '' ) { - return settings.nTable; - } - - var scrollX = scroll.sX; - var scrollY = scroll.sY; - var classes = settings.oClasses; - var caption = table.children('caption'); - var captionSide = caption.length ? caption[0]._captionSide : null; - var headerClone = $( table[0].cloneNode(false) ); - var footerClone = $( table[0].cloneNode(false) ); - var footer = table.children('tfoot'); - var _div = '<div/>'; - var size = function ( s ) { - return !s ? null : _fnStringToCss( s ); - }; - - if ( ! footer.length ) { - footer = null; - } - - /* - * The HTML structure that we want to generate in this function is: - * div - scroller - * div - scroll head - * div - scroll head inner - * table - scroll head table - * thead - thead - * div - scroll body - * table - table (master table) - * thead - thead clone for sizing - * tbody - tbody - * div - scroll foot - * div - scroll foot inner - * table - scroll foot table - * tfoot - tfoot - */ - var scroller = $( _div, { 'class': classes.sScrollWrapper } ) - .append( - $(_div, { 'class': classes.sScrollHead } ) - .css( { - overflow: 'hidden', - position: 'relative', - border: 0, - width: scrollX ? size(scrollX) : '100%' - } ) - .append( - $(_div, { 'class': classes.sScrollHeadInner } ) - .css( { - 'box-sizing': 'content-box', - width: scroll.sXInner || '100%' - } ) - .append( - headerClone - .removeAttr('id') - .css( 'margin-left', 0 ) - .append( captionSide === 'top' ? caption : null ) - .append( - table.children('thead') - ) - ) - ) - ) - .append( - $(_div, { 'class': classes.sScrollBody } ) - .css( { - position: 'relative', - overflow: 'auto', - width: size( scrollX ) - } ) - .append( table ) - ); - - if ( footer ) { - scroller.append( - $(_div, { 'class': classes.sScrollFoot } ) - .css( { - overflow: 'hidden', - border: 0, - width: scrollX ? size(scrollX) : '100%' - } ) - .append( - $(_div, { 'class': classes.sScrollFootInner } ) - .append( - footerClone - .removeAttr('id') - .css( 'margin-left', 0 ) - .append( captionSide === 'bottom' ? caption : null ) - .append( - table.children('tfoot') - ) - ) - ) - ); - } - - var children = scroller.children(); - var scrollHead = children[0]; - var scrollBody = children[1]; - var scrollFoot = footer ? children[2] : null; - - // When the body is scrolled, then we also want to scroll the headers - if ( scrollX ) { - $(scrollBody).on( 'scroll.DT', function (e) { - var scrollLeft = this.scrollLeft; - - scrollHead.scrollLeft = scrollLeft; - - if ( footer ) { - scrollFoot.scrollLeft = scrollLeft; - } - } ); - } - - $(scrollBody).css('max-height', scrollY); - if (! scroll.bCollapse) { - $(scrollBody).css('height', scrollY); - } - - settings.nScrollHead = scrollHead; - settings.nScrollBody = scrollBody; - settings.nScrollFoot = scrollFoot; - - // On redraw - align columns - settings.aoDrawCallback.push( { - "fn": _fnScrollDraw, - "sName": "scrolling" - } ); - - return scroller[0]; - } - - - - /** - * Update the header, footer and body tables for resizing - i.e. column - * alignment. - * - * Welcome to the most horrible function DataTables. The process that this - * function follows is basically: - * 1. Re-create the table inside the scrolling div - * 2. Take live measurements from the DOM - * 3. Apply the measurements to align the columns - * 4. Clean up - * - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnScrollDraw ( settings ) - { - // Given that this is such a monster function, a lot of variables are use - // to try and keep the minimised size as small as possible - var - scroll = settings.oScroll, - scrollX = scroll.sX, - scrollXInner = scroll.sXInner, - scrollY = scroll.sY, - barWidth = scroll.iBarWidth, - divHeader = $(settings.nScrollHead), - divHeaderStyle = divHeader[0].style, - divHeaderInner = divHeader.children('div'), - divHeaderInnerStyle = divHeaderInner[0].style, - divHeaderTable = divHeaderInner.children('table'), - divBodyEl = settings.nScrollBody, - divBody = $(divBodyEl), - divBodyStyle = divBodyEl.style, - divFooter = $(settings.nScrollFoot), - divFooterInner = divFooter.children('div'), - divFooterTable = divFooterInner.children('table'), - header = $(settings.nTHead), - table = $(settings.nTable), - tableEl = table[0], - tableStyle = tableEl.style, - footer = settings.nTFoot ? $(settings.nTFoot) : null, - browser = settings.oBrowser, - ie67 = browser.bScrollOversize, - dtHeaderCells = _pluck( settings.aoColumns, 'nTh' ), - headerTrgEls, footerTrgEls, - headerSrcEls, footerSrcEls, - headerCopy, footerCopy, - headerWidths=[], footerWidths=[], - headerContent=[], footerContent=[], - idx, correction, sanityWidth, - zeroOut = function(nSizer) { - var style = nSizer.style; - style.paddingTop = "0"; - style.paddingBottom = "0"; - style.borderTopWidth = "0"; - style.borderBottomWidth = "0"; - style.height = 0; - }; - - // If the scrollbar visibility has changed from the last draw, we need to - // adjust the column sizes as the table width will have changed to account - // for the scrollbar - var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight; - - if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) { - settings.scrollBarVis = scrollBarVis; - _fnAdjustColumnSizing( settings ); - return; // adjust column sizing will call this function again - } - else { - settings.scrollBarVis = scrollBarVis; - } - - /* - * 1. Re-create the table inside the scrolling div - */ - - // Remove the old minimised thead and tfoot elements in the inner table - table.children('thead, tfoot').remove(); - - if ( footer ) { - footerCopy = footer.clone().prependTo( table ); - footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized - footerSrcEls = footerCopy.find('tr'); - } - - // Clone the current header and footer elements and then place it into the inner table - headerCopy = header.clone().prependTo( table ); - headerTrgEls = header.find('tr'); // original header is in its own table - headerSrcEls = headerCopy.find('tr'); - headerCopy.find('th, td').removeAttr('tabindex'); - - - /* - * 2. Take live measurements from the DOM - do not alter the DOM itself! - */ - - // Remove old sizing and apply the calculated column widths - // Get the unique column headers in the newly created (cloned) header. We want to apply the - // calculated sizes to this header - if ( ! scrollX ) - { - divBodyStyle.width = '100%'; - divHeader[0].style.width = '100%'; - } - - $.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) { - idx = _fnVisibleToColumnIndex( settings, i ); - el.style.width = settings.aoColumns[idx].sWidth; - } ); - - if ( footer ) { - _fnApplyToChildren( function(n) { - n.style.width = ""; - }, footerSrcEls ); - } - - // Size the table as a whole - sanityWidth = table.outerWidth(); - if ( scrollX === "" ) { - // No x scrolling - tableStyle.width = "100%"; - - // IE7 will make the width of the table when 100% include the scrollbar - // - which is shouldn't. When there is a scrollbar we need to take this - // into account. - if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight || - divBody.css('overflow-y') == "scroll") - ) { - tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth); - } - - // Recalculate the sanity width - sanityWidth = table.outerWidth(); - } - else if ( scrollXInner !== "" ) { - // legacy x scroll inner has been given - use it - tableStyle.width = _fnStringToCss(scrollXInner); - - // Recalculate the sanity width - sanityWidth = table.outerWidth(); - } - - // Hidden header should have zero height, so remove padding and borders. Then - // set the width based on the real headers - - // Apply all styles in one pass - _fnApplyToChildren( zeroOut, headerSrcEls ); - - // Read all widths in next pass - _fnApplyToChildren( function(nSizer) { - headerContent.push( nSizer.innerHTML ); - headerWidths.push( _fnStringToCss( $(nSizer).css('width') ) ); - }, headerSrcEls ); - - // Apply all widths in final pass - _fnApplyToChildren( function(nToSize, i) { - // Only apply widths to the DataTables detected header cells - this - // prevents complex headers from having contradictory sizes applied - if ( $.inArray( nToSize, dtHeaderCells ) !== -1 ) { - nToSize.style.width = headerWidths[i]; - } - }, headerTrgEls ); - - $(headerSrcEls).height(0); - - /* Same again with the footer if we have one */ - if ( footer ) - { - _fnApplyToChildren( zeroOut, footerSrcEls ); - - _fnApplyToChildren( function(nSizer) { - footerContent.push( nSizer.innerHTML ); - footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) ); - }, footerSrcEls ); - - _fnApplyToChildren( function(nToSize, i) { - nToSize.style.width = footerWidths[i]; - }, footerTrgEls ); - - $(footerSrcEls).height(0); - } - - - /* - * 3. Apply the measurements - */ - - // "Hide" the header and footer that we used for the sizing. We need to keep - // the content of the cell so that the width applied to the header and body - // both match, but we want to hide it completely. We want to also fix their - // width to what they currently are - _fnApplyToChildren( function(nSizer, i) { - nSizer.innerHTML = '<div class="dataTables_sizing">'+headerContent[i]+'</div>'; - nSizer.childNodes[0].style.height = "0"; - nSizer.childNodes[0].style.overflow = "hidden"; - nSizer.style.width = headerWidths[i]; - }, headerSrcEls ); - - if ( footer ) - { - _fnApplyToChildren( function(nSizer, i) { - nSizer.innerHTML = '<div class="dataTables_sizing">'+footerContent[i]+'</div>'; - nSizer.childNodes[0].style.height = "0"; - nSizer.childNodes[0].style.overflow = "hidden"; - nSizer.style.width = footerWidths[i]; - }, footerSrcEls ); - } - - // Sanity check that the table is of a sensible width. If not then we are going to get - // misalignment - try to prevent this by not allowing the table to shrink below its min width - if ( table.outerWidth() < sanityWidth ) - { - // The min width depends upon if we have a vertical scrollbar visible or not */ - correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight || - divBody.css('overflow-y') == "scroll")) ? - sanityWidth+barWidth : - sanityWidth; - - // IE6/7 are a law unto themselves... - if ( ie67 && (divBodyEl.scrollHeight > - divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll") - ) { - tableStyle.width = _fnStringToCss( correction-barWidth ); - } - - // And give the user a warning that we've stopped the table getting too small - if ( scrollX === "" || scrollXInner !== "" ) { - _fnLog( settings, 1, 'Possible column misalignment', 6 ); - } - } - else - { - correction = '100%'; - } - - // Apply to the container elements - divBodyStyle.width = _fnStringToCss( correction ); - divHeaderStyle.width = _fnStringToCss( correction ); - - if ( footer ) { - settings.nScrollFoot.style.width = _fnStringToCss( correction ); - } - - - /* - * 4. Clean up - */ - if ( ! scrollY ) { - /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting - * the scrollbar height from the visible display, rather than adding it on. We need to - * set the height in order to sort this. Don't want to do it in any other browsers. - */ - if ( ie67 ) { - divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth ); - } - } - - /* Finally set the width's of the header and footer tables */ - var iOuterWidth = table.outerWidth(); - divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth ); - divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth ); - - // Figure out if there are scrollbar present - if so then we need a the header and footer to - // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) - var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll"; - var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' ); - divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px"; - - if ( footer ) { - divFooterTable[0].style.width = _fnStringToCss( iOuterWidth ); - divFooterInner[0].style.width = _fnStringToCss( iOuterWidth ); - divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px"; - } - - // Correct DOM ordering for colgroup - comes before the thead - table.children('colgroup').insertBefore( table.children('thead') ); - - /* Adjust the position of the header in case we loose the y-scrollbar */ - divBody.trigger('scroll'); - - // If sorting or filtering has occurred, jump the scrolling back to the top - // only if we aren't holding the position - if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) { - divBodyEl.scrollTop = 0; - } - } - - - - /** - * Apply a given function to the display child nodes of an element array (typically - * TD children of TR rows - * @param {function} fn Method to apply to the objects - * @param array {nodes} an1 List of elements to look through for display children - * @param array {nodes} an2 Another list (identical structure to the first) - optional - * @memberof DataTable#oApi - */ - function _fnApplyToChildren( fn, an1, an2 ) - { - var index=0, i=0, iLen=an1.length; - var nNode1, nNode2; - - while ( i < iLen ) { - nNode1 = an1[i].firstChild; - nNode2 = an2 ? an2[i].firstChild : null; - - while ( nNode1 ) { - if ( nNode1.nodeType === 1 ) { - if ( an2 ) { - fn( nNode1, nNode2, index ); - } - else { - fn( nNode1, index ); - } - - index++; - } - - nNode1 = nNode1.nextSibling; - nNode2 = an2 ? nNode2.nextSibling : null; - } - - i++; - } - } - - - - var __re_html_remove = /<.*?>/g; - - - /** - * Calculate the width of columns for the table - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnCalculateColumnWidths ( oSettings ) - { - var - table = oSettings.nTable, - columns = oSettings.aoColumns, - scroll = oSettings.oScroll, - scrollY = scroll.sY, - scrollX = scroll.sX, - scrollXInner = scroll.sXInner, - columnCount = columns.length, - visibleColumns = _fnGetColumns( oSettings, 'bVisible' ), - headerCells = $('th', oSettings.nTHead), - tableWidthAttr = table.getAttribute('width'), // from DOM element - tableContainer = table.parentNode, - userInputs = false, - i, column, columnIdx, width, outerWidth, - browser = oSettings.oBrowser, - ie67 = browser.bScrollOversize; - - var styleWidth = table.style.width; - if ( styleWidth && styleWidth.indexOf('%') !== -1 ) { - tableWidthAttr = styleWidth; - } - - /* Convert any user input sizes into pixel sizes */ - for ( i=0 ; i<visibleColumns.length ; i++ ) { - column = columns[ visibleColumns[i] ]; - - if ( column.sWidth !== null ) { - column.sWidth = _fnConvertToWidth( column.sWidthOrig, tableContainer ); - - userInputs = true; - } - } - - /* If the number of columns in the DOM equals the number that we have to - * process in DataTables, then we can use the offsets that are created by - * the web- browser. No custom sizes can be set in order for this to happen, - * nor scrolling used - */ - if ( ie67 || ! userInputs && ! scrollX && ! scrollY && - columnCount == _fnVisbleColumns( oSettings ) && - columnCount == headerCells.length - ) { - for ( i=0 ; i<columnCount ; i++ ) { - var colIdx = _fnVisibleToColumnIndex( oSettings, i ); - - if ( colIdx !== null ) { - columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() ); - } - } - } - else - { - // Otherwise construct a single row, worst case, table with the widest - // node in the data, assign any user defined widths, then insert it into - // the DOM and allow the browser to do all the hard work of calculating - // table widths - var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table - .css( 'visibility', 'hidden' ) - .removeAttr( 'id' ); - - // Clean up the table body - tmpTable.find('tbody tr').remove(); - var tr = $('<tr/>').appendTo( tmpTable.find('tbody') ); - - // Clone the table header and footer - we can't use the header / footer - // from the cloned table, since if scrolling is active, the table's - // real header and footer are contained in different table tags - tmpTable.find('thead, tfoot').remove(); - tmpTable - .append( $(oSettings.nTHead).clone() ) - .append( $(oSettings.nTFoot).clone() ); - - // Remove any assigned widths from the footer (from scrolling) - tmpTable.find('tfoot th, tfoot td').css('width', ''); - - // Apply custom sizing to the cloned header - headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] ); - - for ( i=0 ; i<visibleColumns.length ; i++ ) { - column = columns[ visibleColumns[i] ]; - - headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ? - _fnStringToCss( column.sWidthOrig ) : - ''; - - // For scrollX we need to force the column width otherwise the - // browser will collapse it. If this width is smaller than the - // width the column requires, then it will have no effect - if ( column.sWidthOrig && scrollX ) { - $( headerCells[i] ).append( $('<div/>').css( { - width: column.sWidthOrig, - margin: 0, - padding: 0, - border: 0, - height: 1 - } ) ); - } - } - - // Find the widest cell for each column and put it into the table - if ( oSettings.aoData.length ) { - for ( i=0 ; i<visibleColumns.length ; i++ ) { - columnIdx = visibleColumns[i]; - column = columns[ columnIdx ]; - - $( _fnGetWidestNode( oSettings, columnIdx ) ) - .clone( false ) - .append( column.sContentPadding ) - .appendTo( tr ); - } - } - - // Tidy the temporary table - remove name attributes so there aren't - // duplicated in the dom (radio elements for example) - $('[name]', tmpTable).removeAttr('name'); - - // Table has been built, attach to the document so we can work with it. - // A holding element is used, positioned at the top of the container - // with minimal height, so it has no effect on if the container scrolls - // or not. Otherwise it might trigger scrolling when it actually isn't - // needed - var holder = $('<div/>').css( scrollX || scrollY ? - { - position: 'absolute', - top: 0, - left: 0, - height: 1, - right: 0, - overflow: 'hidden' - } : - {} - ) - .append( tmpTable ) - .appendTo( tableContainer ); - - // When scrolling (X or Y) we want to set the width of the table as - // appropriate. However, when not scrolling leave the table width as it - // is. This results in slightly different, but I think correct behaviour - if ( scrollX && scrollXInner ) { - tmpTable.width( scrollXInner ); - } - else if ( scrollX ) { - tmpTable.css( 'width', 'auto' ); - tmpTable.removeAttr('width'); - - // If there is no width attribute or style, then allow the table to - // collapse - if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) { - tmpTable.width( tableContainer.clientWidth ); - } - } - else if ( scrollY ) { - tmpTable.width( tableContainer.clientWidth ); - } - else if ( tableWidthAttr ) { - tmpTable.width( tableWidthAttr ); - } - - // Get the width of each column in the constructed table - we need to - // know the inner width (so it can be assigned to the other table's - // cells) and the outer width so we can calculate the full width of the - // table. This is safe since DataTables requires a unique cell for each - // column, but if ever a header can span multiple columns, this will - // need to be modified. - var total = 0; - for ( i=0 ; i<visibleColumns.length ; i++ ) { - var cell = $(headerCells[i]); - var border = cell.outerWidth() - cell.width(); - - // Use getBounding... where possible (not IE8-) because it can give - // sub-pixel accuracy, which we then want to round up! - var bounding = browser.bBounding ? - Math.ceil( headerCells[i].getBoundingClientRect().width ) : - cell.outerWidth(); - - // Total is tracked to remove any sub-pixel errors as the outerWidth - // of the table might not equal the total given here (IE!). - total += bounding; - - // Width for each column to use - columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border ); - } - - table.style.width = _fnStringToCss( total ); - - // Finished with the table - ditch it - holder.remove(); - } - - // If there is a width attr, we want to attach an event listener which - // allows the table sizing to automatically adjust when the window is - // resized. Use the width attr rather than CSS, since we can't know if the - // CSS is a relative value or absolute - DOM read is always px. - if ( tableWidthAttr ) { - table.style.width = _fnStringToCss( tableWidthAttr ); - } - - if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) { - var bindResize = function () { - $(window).on('resize.DT-'+oSettings.sInstance, _fnThrottle( function () { - _fnAdjustColumnSizing( oSettings ); - } ) ); - }; - - // IE6/7 will crash if we bind a resize event handler on page load. - // To be removed in 1.11 which drops IE6/7 support - if ( ie67 ) { - setTimeout( bindResize, 1000 ); - } - else { - bindResize(); - } - - oSettings._reszEvt = true; - } - } - - - /** - * Throttle the calls to a function. Arguments and context are maintained for - * the throttled function - * @param {function} fn Function to be called - * @param {int} [freq=200] call frequency in mS - * @returns {function} wrapped function - * @memberof DataTable#oApi - */ - var _fnThrottle = DataTable.util.throttle; - - - /** - * Convert a CSS unit width to pixels (e.g. 2em) - * @param {string} width width to be converted - * @param {node} parent parent to get the with for (required for relative widths) - optional - * @returns {int} width in pixels - * @memberof DataTable#oApi - */ - function _fnConvertToWidth ( width, parent ) - { - if ( ! width ) { - return 0; - } - - var n = $('<div/>') - .css( 'width', _fnStringToCss( width ) ) - .appendTo( parent || document.body ); - - var val = n[0].offsetWidth; - n.remove(); - - return val; - } - - - /** - * Get the widest node - * @param {object} settings dataTables settings object - * @param {int} colIdx column of interest - * @returns {node} widest table node - * @memberof DataTable#oApi - */ - function _fnGetWidestNode( settings, colIdx ) - { - var idx = _fnGetMaxLenString( settings, colIdx ); - if ( idx < 0 ) { - return null; - } - - var data = settings.aoData[ idx ]; - return ! data.nTr ? // Might not have been created when deferred rendering - $('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] : - data.anCells[ colIdx ]; - } - - - /** - * Get the maximum strlen for each data column - * @param {object} settings dataTables settings object - * @param {int} colIdx column of interest - * @returns {string} max string length for each column - * @memberof DataTable#oApi - */ - function _fnGetMaxLenString( settings, colIdx ) - { - var s, max=-1, maxIdx = -1; - - for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { - s = _fnGetCellData( settings, i, colIdx, 'display' )+''; - s = s.replace( __re_html_remove, '' ); - s = s.replace( / /g, ' ' ); - - if ( s.length > max ) { - max = s.length; - maxIdx = i; - } - } - - return maxIdx; - } - - - /** - * Append a CSS unit (only if required) to a string - * @param {string} value to css-ify - * @returns {string} value with css unit - * @memberof DataTable#oApi - */ - function _fnStringToCss( s ) - { - if ( s === null ) { - return '0px'; - } - - if ( typeof s == 'number' ) { - return s < 0 ? - '0px' : - s+'px'; - } - - // Check it has a unit character already - return s.match(/\d$/) ? - s+'px' : - s; - } - - - - function _fnSortFlatten ( settings ) - { - var - i, iLen, k, kLen, - aSort = [], - aiOrig = [], - aoColumns = settings.aoColumns, - aDataSort, iCol, sType, srcCol, - fixed = settings.aaSortingFixed, - fixedObj = $.isPlainObject( fixed ), - nestedSort = [], - add = function ( a ) { - if ( a.length && ! Array.isArray( a[0] ) ) { - // 1D array - nestedSort.push( a ); - } - else { - // 2D array - $.merge( nestedSort, a ); - } - }; - - // Build the sort array, with pre-fix and post-fix options if they have been - // specified - if ( Array.isArray( fixed ) ) { - add( fixed ); - } - - if ( fixedObj && fixed.pre ) { - add( fixed.pre ); - } - - add( settings.aaSorting ); - - if (fixedObj && fixed.post ) { - add( fixed.post ); - } - - for ( i=0 ; i<nestedSort.length ; i++ ) - { - srcCol = nestedSort[i][0]; - aDataSort = aoColumns[ srcCol ].aDataSort; - - for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ ) - { - iCol = aDataSort[k]; - sType = aoColumns[ iCol ].sType || 'string'; - - if ( nestedSort[i]._idx === undefined ) { - nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting ); - } - - aSort.push( { - src: srcCol, - col: iCol, - dir: nestedSort[i][1], - index: nestedSort[i]._idx, - type: sType, - formatter: DataTable.ext.type.order[ sType+"-pre" ] - } ); - } - } - - return aSort; - } - - /** - * Change the order of the table - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - * @todo This really needs split up! - */ - function _fnSort ( oSettings ) - { - var - i, ien, iLen, j, jLen, k, kLen, - sDataType, nTh, - aiOrig = [], - oExtSort = DataTable.ext.type.order, - aoData = oSettings.aoData, - aoColumns = oSettings.aoColumns, - aDataSort, data, iCol, sType, oSort, - formatters = 0, - sortCol, - displayMaster = oSettings.aiDisplayMaster, - aSort; - - // Resolve any column types that are unknown due to addition or invalidation - // @todo Can this be moved into a 'data-ready' handler which is called when - // data is going to be used in the table? - _fnColumnTypes( oSettings ); - - aSort = _fnSortFlatten( oSettings ); - - for ( i=0, ien=aSort.length ; i<ien ; i++ ) { - sortCol = aSort[i]; - - // Track if we can use the fast sort algorithm - if ( sortCol.formatter ) { - formatters++; - } - - // Load the data needed for the sort, for each cell - _fnSortData( oSettings, sortCol.col ); - } - - /* No sorting required if server-side or no sorting array */ - if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 ) - { - // Create a value - key array of the current row positions such that we can use their - // current position during the sort, if values match, in order to perform stable sorting - for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) { - aiOrig[ displayMaster[i] ] = i; - } - - /* Do the sort - here we want multi-column sorting based on a given data source (column) - * and sorting function (from oSort) in a certain direction. It's reasonably complex to - * follow on it's own, but this is what we want (example two column sorting): - * fnLocalSorting = function(a,b){ - * var iTest; - * iTest = oSort['string-asc']('data11', 'data12'); - * if (iTest !== 0) - * return iTest; - * iTest = oSort['numeric-desc']('data21', 'data22'); - * if (iTest !== 0) - * return iTest; - * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] ); - * } - * Basically we have a test for each sorting column, if the data in that column is equal, - * test the next column. If all columns match, then we use a numeric sort on the row - * positions in the original data array to provide a stable sort. - * - * Note - I know it seems excessive to have two sorting methods, but the first is around - * 15% faster, so the second is only maintained for backwards compatibility with sorting - * methods which do not have a pre-sort formatting function. - */ - if ( formatters === aSort.length ) { - // All sort types have formatting functions - displayMaster.sort( function ( a, b ) { - var - x, y, k, test, sort, - len=aSort.length, - dataA = aoData[a]._aSortData, - dataB = aoData[b]._aSortData; - - for ( k=0 ; k<len ; k++ ) { - sort = aSort[k]; - - x = dataA[ sort.col ]; - y = dataB[ sort.col ]; - - test = x<y ? -1 : x>y ? 1 : 0; - if ( test !== 0 ) { - return sort.dir === 'asc' ? test : -test; - } - } - - x = aiOrig[a]; - y = aiOrig[b]; - return x<y ? -1 : x>y ? 1 : 0; - } ); - } - else { - // Depreciated - remove in 1.11 (providing a plug-in option) - // Not all sort types have formatting methods, so we have to call their sorting - // methods. - displayMaster.sort( function ( a, b ) { - var - x, y, k, l, test, sort, fn, - len=aSort.length, - dataA = aoData[a]._aSortData, - dataB = aoData[b]._aSortData; - - for ( k=0 ; k<len ; k++ ) { - sort = aSort[k]; - - x = dataA[ sort.col ]; - y = dataB[ sort.col ]; - - fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ]; - test = fn( x, y ); - if ( test !== 0 ) { - return test; - } - } - - x = aiOrig[a]; - y = aiOrig[b]; - return x<y ? -1 : x>y ? 1 : 0; - } ); - } - } - - /* Tell the draw function that we have sorted the data */ - oSettings.bSorted = true; - } - - - function _fnSortAria ( settings ) - { - var label; - var nextSort; - var columns = settings.aoColumns; - var aSort = _fnSortFlatten( settings ); - var oAria = settings.oLanguage.oAria; - - // ARIA attributes - need to loop all columns, to update all (removing old - // attributes as needed) - for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) - { - var col = columns[i]; - var asSorting = col.asSorting; - var sTitle = col.sTitle.replace( /<.*?>/g, "" ); - var th = col.nTh; - - // IE7 is throwing an error when setting these properties with jQuery's - // attr() and removeAttr() methods... - th.removeAttribute('aria-sort'); - - /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */ - if ( col.bSortable ) { - if ( aSort.length > 0 && aSort[0].col == i ) { - th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" ); - nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0]; - } - else { - nextSort = asSorting[0]; - } - - label = sTitle + ( nextSort === "asc" ? - oAria.sSortAscending : - oAria.sSortDescending - ); - } - else { - label = sTitle; - } - - th.setAttribute('aria-label', label); - } - } - - - /** - * Function to run on user sort request - * @param {object} settings dataTables settings object - * @param {node} attachTo node to attach the handler to - * @param {int} colIdx column sorting index - * @param {boolean} [append=false] Append the requested sort to the existing - * sort if true (i.e. multi-column sort) - * @param {function} [callback] callback function - * @memberof DataTable#oApi - */ - function _fnSortListener ( settings, colIdx, append, callback ) - { - var col = settings.aoColumns[ colIdx ]; - var sorting = settings.aaSorting; - var asSorting = col.asSorting; - var nextSortIdx; - var next = function ( a, overflow ) { - var idx = a._idx; - if ( idx === undefined ) { - idx = $.inArray( a[1], asSorting ); - } - - return idx+1 < asSorting.length ? - idx+1 : - overflow ? - null : - 0; - }; - - // Convert to 2D array if needed - if ( typeof sorting[0] === 'number' ) { - sorting = settings.aaSorting = [ sorting ]; - } - - // If appending the sort then we are multi-column sorting - if ( append && settings.oFeatures.bSortMulti ) { - // Are we already doing some kind of sort on this column? - var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') ); - - if ( sortIdx !== -1 ) { - // Yes, modify the sort - nextSortIdx = next( sorting[sortIdx], true ); - - if ( nextSortIdx === null && sorting.length === 1 ) { - nextSortIdx = 0; // can't remove sorting completely - } - - if ( nextSortIdx === null ) { - sorting.splice( sortIdx, 1 ); - } - else { - sorting[sortIdx][1] = asSorting[ nextSortIdx ]; - sorting[sortIdx]._idx = nextSortIdx; - } - } - else { - // No sort on this column yet - sorting.push( [ colIdx, asSorting[0], 0 ] ); - sorting[sorting.length-1]._idx = 0; - } - } - else if ( sorting.length && sorting[0][0] == colIdx ) { - // Single column - already sorting on this column, modify the sort - nextSortIdx = next( sorting[0] ); - - sorting.length = 1; - sorting[0][1] = asSorting[ nextSortIdx ]; - sorting[0]._idx = nextSortIdx; - } - else { - // Single column - sort only on this column - sorting.length = 0; - sorting.push( [ colIdx, asSorting[0] ] ); - sorting[0]._idx = 0; - } - - // Run the sort by calling a full redraw - _fnReDraw( settings ); - - // callback used for async user interaction - if ( typeof callback == 'function' ) { - callback( settings ); - } - } - - - /** - * Attach a sort handler (click) to a node - * @param {object} settings dataTables settings object - * @param {node} attachTo node to attach the handler to - * @param {int} colIdx column sorting index - * @param {function} [callback] callback function - * @memberof DataTable#oApi - */ - function _fnSortAttachListener ( settings, attachTo, colIdx, callback ) - { - var col = settings.aoColumns[ colIdx ]; - - _fnBindAction( attachTo, {}, function (e) { - /* If the column is not sortable - don't to anything */ - if ( col.bSortable === false ) { - return; - } - - // If processing is enabled use a timeout to allow the processing - // display to be shown - otherwise to it synchronously - if ( settings.oFeatures.bProcessing ) { - _fnProcessingDisplay( settings, true ); - - setTimeout( function() { - _fnSortListener( settings, colIdx, e.shiftKey, callback ); - - // In server-side processing, the draw callback will remove the - // processing display - if ( _fnDataSource( settings ) !== 'ssp' ) { - _fnProcessingDisplay( settings, false ); - } - }, 0 ); - } - else { - _fnSortListener( settings, colIdx, e.shiftKey, callback ); - } - } ); - } - - - /** - * Set the sorting classes on table's body, Note: it is safe to call this function - * when bSort and bSortClasses are false - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnSortingClasses( settings ) - { - var oldSort = settings.aLastSort; - var sortClass = settings.oClasses.sSortColumn; - var sort = _fnSortFlatten( settings ); - var features = settings.oFeatures; - var i, ien, colIdx; - - if ( features.bSort && features.bSortClasses ) { - // Remove old sorting classes - for ( i=0, ien=oldSort.length ; i<ien ; i++ ) { - colIdx = oldSort[i].src; - - // Remove column sorting - $( _pluck( settings.aoData, 'anCells', colIdx ) ) - .removeClass( sortClass + (i<2 ? i+1 : 3) ); - } - - // Add new column sorting - for ( i=0, ien=sort.length ; i<ien ; i++ ) { - colIdx = sort[i].src; - - $( _pluck( settings.aoData, 'anCells', colIdx ) ) - .addClass( sortClass + (i<2 ? i+1 : 3) ); - } - } - - settings.aLastSort = sort; - } - - - // Get the data to sort a column, be it from cache, fresh (populating the - // cache), or from a sort formatter - function _fnSortData( settings, idx ) - { - // Custom sorting function - provided by the sort data type - var column = settings.aoColumns[ idx ]; - var customSort = DataTable.ext.order[ column.sSortDataType ]; - var customData; - - if ( customSort ) { - customData = customSort.call( settings.oInstance, settings, idx, - _fnColumnIndexToVisible( settings, idx ) - ); - } - - // Use / populate cache - var row, cellData; - var formatter = DataTable.ext.type.order[ column.sType+"-pre" ]; - - for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { - row = settings.aoData[i]; - - if ( ! row._aSortData ) { - row._aSortData = []; - } - - if ( ! row._aSortData[idx] || customSort ) { - cellData = customSort ? - customData[i] : // If there was a custom sort function, use data from there - _fnGetCellData( settings, i, idx, 'sort' ); - - row._aSortData[ idx ] = formatter ? - formatter( cellData ) : - cellData; - } - } - } - - - - /** - * Save the state of a table - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnSaveState ( settings ) - { - if ( !settings.oFeatures.bStateSave || settings.bDestroying ) - { - return; - } - - /* Store the interesting variables */ - var state = { - time: +new Date(), - start: settings._iDisplayStart, - length: settings._iDisplayLength, - order: $.extend( true, [], settings.aaSorting ), - search: _fnSearchToCamel( settings.oPreviousSearch ), - columns: $.map( settings.aoColumns, function ( col, i ) { - return { - visible: col.bVisible, - search: _fnSearchToCamel( settings.aoPreSearchCols[i] ) - }; - } ) - }; - - _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] ); - - settings.oSavedState = state; - settings.fnStateSaveCallback.call( settings.oInstance, settings, state ); - } - - - /** - * Attempt to load a saved table state - * @param {object} oSettings dataTables settings object - * @param {object} oInit DataTables init object so we can override settings - * @param {function} callback Callback to execute when the state has been loaded - * @memberof DataTable#oApi - */ - function _fnLoadState ( settings, oInit, callback ) - { - var i, ien; - var columns = settings.aoColumns; - var loaded = function ( s ) { - if ( ! s || ! s.time ) { - callback(); - return; - } - - // Allow custom and plug-in manipulation functions to alter the saved data set and - // cancelling of loading by returning false - var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] ); - if ( $.inArray( false, abStateLoad ) !== -1 ) { - callback(); - return; - } - - // Reject old data - var duration = settings.iStateDuration; - if ( duration > 0 && s.time < +new Date() - (duration*1000) ) { - callback(); - return; - } - - // Number of columns have changed - all bets are off, no restore of settings - if ( s.columns && columns.length !== s.columns.length ) { - callback(); - return; - } - - // Store the saved state so it might be accessed at any time - settings.oLoadedState = $.extend( true, {}, s ); - - // Restore key features - todo - for 1.11 this needs to be done by - // subscribed events - if ( s.start !== undefined ) { - settings._iDisplayStart = s.start; - settings.iInitDisplayStart = s.start; - } - if ( s.length !== undefined ) { - settings._iDisplayLength = s.length; - } - - // Order - if ( s.order !== undefined ) { - settings.aaSorting = []; - $.each( s.order, function ( i, col ) { - settings.aaSorting.push( col[0] >= columns.length ? - [ 0, col[1] ] : - col - ); - } ); - } - - // Search - if ( s.search !== undefined ) { - $.extend( settings.oPreviousSearch, _fnSearchToHung( s.search ) ); - } - - // Columns - // - if ( s.columns ) { - for ( i=0, ien=s.columns.length ; i<ien ; i++ ) { - var col = s.columns[i]; - - // Visibility - if ( col.visible !== undefined ) { - columns[i].bVisible = col.visible; - } - - // Search - if ( col.search !== undefined ) { - $.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) ); - } - } - } - - _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] ); - callback(); - }; - - if ( ! settings.oFeatures.bStateSave ) { - callback(); - return; - } - - var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded ); - - if ( state !== undefined ) { - loaded( state ); - } - // otherwise, wait for the loaded callback to be executed - } - - - /** - * Return the settings object for a particular table - * @param {node} table table we are using as a dataTable - * @returns {object} Settings object - or null if not found - * @memberof DataTable#oApi - */ - function _fnSettingsFromNode ( table ) - { - var settings = DataTable.settings; - var idx = $.inArray( table, _pluck( settings, 'nTable' ) ); - - return idx !== -1 ? - settings[ idx ] : - null; - } - - - /** - * Log an error message - * @param {object} settings dataTables settings object - * @param {int} level log error messages, or display them to the user - * @param {string} msg error message - * @param {int} tn Technical note id to get more information about the error. - * @memberof DataTable#oApi - */ - function _fnLog( settings, level, msg, tn ) - { - msg = 'DataTables warning: '+ - (settings ? 'table id='+settings.sTableId+' - ' : '')+msg; - - if ( tn ) { - msg += '. For more information about this error, please see '+ - 'http://datatables.net/tn/'+tn; - } - - if ( ! level ) { - // Backwards compatibility pre 1.10 - var ext = DataTable.ext; - var type = ext.sErrMode || ext.errMode; - - if ( settings ) { - _fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] ); - } - - if ( type == 'alert' ) { - alert( msg ); - } - else if ( type == 'throw' ) { - throw new Error(msg); - } - else if ( typeof type == 'function' ) { - type( settings, tn, msg ); - } - } - else if ( window.console && console.log ) { - console.log( msg ); - } - } - - - /** - * See if a property is defined on one object, if so assign it to the other object - * @param {object} ret target object - * @param {object} src source object - * @param {string} name property - * @param {string} [mappedName] name to map too - optional, name used if not given - * @memberof DataTable#oApi - */ - function _fnMap( ret, src, name, mappedName ) - { - if ( Array.isArray( name ) ) { - $.each( name, function (i, val) { - if ( Array.isArray( val ) ) { - _fnMap( ret, src, val[0], val[1] ); - } - else { - _fnMap( ret, src, val ); - } - } ); - - return; - } - - if ( mappedName === undefined ) { - mappedName = name; - } - - if ( src[name] !== undefined ) { - ret[mappedName] = src[name]; - } - } - - - /** - * Extend objects - very similar to jQuery.extend, but deep copy objects, and - * shallow copy arrays. The reason we need to do this, is that we don't want to - * deep copy array init values (such as aaSorting) since the dev wouldn't be - * able to override them, but we do want to deep copy arrays. - * @param {object} out Object to extend - * @param {object} extender Object from which the properties will be applied to - * out - * @param {boolean} breakRefs If true, then arrays will be sliced to take an - * independent copy with the exception of the `data` or `aaData` parameters - * if they are present. This is so you can pass in a collection to - * DataTables and have that used as your data source without breaking the - * references - * @returns {object} out Reference, just for convenience - out === the return. - * @memberof DataTable#oApi - * @todo This doesn't take account of arrays inside the deep copied objects. - */ - function _fnExtend( out, extender, breakRefs ) - { - var val; - - for ( var prop in extender ) { - if ( extender.hasOwnProperty(prop) ) { - val = extender[prop]; - - if ( $.isPlainObject( val ) ) { - if ( ! $.isPlainObject( out[prop] ) ) { - out[prop] = {}; - } - $.extend( true, out[prop], val ); - } - else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && Array.isArray(val) ) { - out[prop] = val.slice(); - } - else { - out[prop] = val; - } - } - } - - return out; - } - - - /** - * Bind an event handers to allow a click or return key to activate the callback. - * This is good for accessibility since a return on the keyboard will have the - * same effect as a click, if the element has focus. - * @param {element} n Element to bind the action to - * @param {object} oData Data object to pass to the triggered function - * @param {function} fn Callback function for when the event is triggered - * @memberof DataTable#oApi - */ - function _fnBindAction( n, oData, fn ) - { - $(n) - .on( 'click.DT', oData, function (e) { - $(n).trigger('blur'); // Remove focus outline for mouse users - fn(e); - } ) - .on( 'keypress.DT', oData, function (e){ - if ( e.which === 13 ) { - e.preventDefault(); - fn(e); - } - } ) - .on( 'selectstart.DT', function () { - /* Take the brutal approach to cancelling text selection */ - return false; - } ); - } - - - /** - * Register a callback function. Easily allows a callback function to be added to - * an array store of callback functions that can then all be called together. - * @param {object} oSettings dataTables settings object - * @param {string} sStore Name of the array storage for the callbacks in oSettings - * @param {function} fn Function to be called back - * @param {string} sName Identifying name for the callback (i.e. a label) - * @memberof DataTable#oApi - */ - function _fnCallbackReg( oSettings, sStore, fn, sName ) - { - if ( fn ) - { - oSettings[sStore].push( { - "fn": fn, - "sName": sName - } ); - } - } - - - /** - * Fire callback functions and trigger events. Note that the loop over the - * callback array store is done backwards! Further note that you do not want to - * fire off triggers in time sensitive applications (for example cell creation) - * as its slow. - * @param {object} settings dataTables settings object - * @param {string} callbackArr Name of the array storage for the callbacks in - * oSettings - * @param {string} eventName Name of the jQuery custom event to trigger. If - * null no trigger is fired - * @param {array} args Array of arguments to pass to the callback function / - * trigger - * @memberof DataTable#oApi - */ - function _fnCallbackFire( settings, callbackArr, eventName, args ) - { - var ret = []; - - if ( callbackArr ) { - ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) { - return val.fn.apply( settings.oInstance, args ); - } ); - } - - if ( eventName !== null ) { - var e = $.Event( eventName+'.dt' ); - - $(settings.nTable).trigger( e, args ); - - ret.push( e.result ); - } - - return ret; - } - - - function _fnLengthOverflow ( settings ) - { - var - start = settings._iDisplayStart, - end = settings.fnDisplayEnd(), - len = settings._iDisplayLength; - - /* If we have space to show extra rows (backing up from the end point - then do so */ - if ( start >= end ) - { - start = end - len; - } - - // Keep the start record on the current page - start -= (start % len); - - if ( len === -1 || start < 0 ) - { - start = 0; - } - - settings._iDisplayStart = start; - } - - - function _fnRenderer( settings, type ) - { - var renderer = settings.renderer; - var host = DataTable.ext.renderer[type]; - - if ( $.isPlainObject( renderer ) && renderer[type] ) { - // Specific renderer for this type. If available use it, otherwise use - // the default. - return host[renderer[type]] || host._; - } - else if ( typeof renderer === 'string' ) { - // Common renderer - if there is one available for this type use it, - // otherwise use the default - return host[renderer] || host._; - } - - // Use the default - return host._; - } - - - /** - * Detect the data source being used for the table. Used to simplify the code - * a little (ajax) and to make it compress a little smaller. - * - * @param {object} settings dataTables settings object - * @returns {string} Data source - * @memberof DataTable#oApi - */ - function _fnDataSource ( settings ) - { - if ( settings.oFeatures.bServerSide ) { - return 'ssp'; - } - else if ( settings.ajax || settings.sAjaxSource ) { - return 'ajax'; - } - return 'dom'; - } - - - - - /** - * Computed structure of the DataTables API, defined by the options passed to - * `DataTable.Api.register()` when building the API. - * - * The structure is built in order to speed creation and extension of the Api - * objects since the extensions are effectively pre-parsed. - * - * The array is an array of objects with the following structure, where this - * base array represents the Api prototype base: - * - * [ - * { - * name: 'data' -- string - Property name - * val: function () {}, -- function - Api method (or undefined if just an object - * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result - * propExt: [ ... ] -- array - Array of Api object definitions to extend the property - * }, - * { - * name: 'row' - * val: {}, - * methodExt: [ ... ], - * propExt: [ - * { - * name: 'data' - * val: function () {}, - * methodExt: [ ... ], - * propExt: [ ... ] - * }, - * ... - * ] - * } - * ] - * - * @type {Array} - * @ignore - */ - var __apiStruct = []; - - - /** - * `Array.prototype` reference. - * - * @type object - * @ignore - */ - var __arrayProto = Array.prototype; - - - /** - * Abstraction for `context` parameter of the `Api` constructor to allow it to - * take several different forms for ease of use. - * - * Each of the input parameter types will be converted to a DataTables settings - * object where possible. - * - * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one - * of: - * - * * `string` - jQuery selector. Any DataTables' matching the given selector - * with be found and used. - * * `node` - `TABLE` node which has already been formed into a DataTable. - * * `jQuery` - A jQuery object of `TABLE` nodes. - * * `object` - DataTables settings object - * * `DataTables.Api` - API instance - * @return {array|null} Matching DataTables settings objects. `null` or - * `undefined` is returned if no matching DataTable is found. - * @ignore - */ - var _toSettings = function ( mixed ) - { - var idx, jq; - var settings = DataTable.settings; - var tables = $.map( settings, function (el, i) { - return el.nTable; - } ); - - if ( ! mixed ) { - return []; - } - else if ( mixed.nTable && mixed.oApi ) { - // DataTables settings object - return [ mixed ]; - } - else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) { - // Table node - idx = $.inArray( mixed, tables ); - return idx !== -1 ? [ settings[idx] ] : null; - } - else if ( mixed && typeof mixed.settings === 'function' ) { - return mixed.settings().toArray(); - } - else if ( typeof mixed === 'string' ) { - // jQuery selector - jq = $(mixed); - } - else if ( mixed instanceof $ ) { - // jQuery object (also DataTables instance) - jq = mixed; - } - - if ( jq ) { - return jq.map( function(i) { - idx = $.inArray( this, tables ); - return idx !== -1 ? settings[idx] : null; - } ).toArray(); - } - }; - - - /** - * DataTables API class - used to control and interface with one or more - * DataTables enhanced tables. - * - * The API class is heavily based on jQuery, presenting a chainable interface - * that you can use to interact with tables. Each instance of the API class has - * a "context" - i.e. the tables that it will operate on. This could be a single - * table, all tables on a page or a sub-set thereof. - * - * Additionally the API is designed to allow you to easily work with the data in - * the tables, retrieving and manipulating it as required. This is done by - * presenting the API class as an array like interface. The contents of the - * array depend upon the actions requested by each method (for example - * `rows().nodes()` will return an array of nodes, while `rows().data()` will - * return an array of objects or arrays depending upon your table's - * configuration). The API object has a number of array like methods (`push`, - * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, - * `unique` etc) to assist your working with the data held in a table. - * - * Most methods (those which return an Api instance) are chainable, which means - * the return from a method call also has all of the methods available that the - * top level object had. For example, these two calls are equivalent: - * - * // Not chained - * api.row.add( {...} ); - * api.draw(); - * - * // Chained - * api.row.add( {...} ).draw(); - * - * @class DataTable.Api - * @param {array|object|string|jQuery} context DataTable identifier. This is - * used to define which DataTables enhanced tables this API will operate on. - * Can be one of: - * - * * `string` - jQuery selector. Any DataTables' matching the given selector - * with be found and used. - * * `node` - `TABLE` node which has already been formed into a DataTable. - * * `jQuery` - A jQuery object of `TABLE` nodes. - * * `object` - DataTables settings object - * @param {array} [data] Data to initialise the Api instance with. - * - * @example - * // Direct initialisation during DataTables construction - * var api = $('#example').DataTable(); - * - * @example - * // Initialisation using a DataTables jQuery object - * var api = $('#example').dataTable().api(); - * - * @example - * // Initialisation as a constructor - * var api = new $.fn.DataTable.Api( 'table.dataTable' ); - */ - _Api = function ( context, data ) - { - if ( ! (this instanceof _Api) ) { - return new _Api( context, data ); - } - - var settings = []; - var ctxSettings = function ( o ) { - var a = _toSettings( o ); - if ( a ) { - settings.push.apply( settings, a ); - } - }; - - if ( Array.isArray( context ) ) { - for ( var i=0, ien=context.length ; i<ien ; i++ ) { - ctxSettings( context[i] ); - } - } - else { - ctxSettings( context ); - } - - // Remove duplicates - this.context = _unique( settings ); - - // Initial data - if ( data ) { - $.merge( this, data ); - } - - // selector - this.selector = { - rows: null, - cols: null, - opts: null - }; - - _Api.extend( this, this, __apiStruct ); - }; - - DataTable.Api = _Api; - - // Don't destroy the existing prototype, just extend it. Required for jQuery 2's - // isPlainObject. - $.extend( _Api.prototype, { - any: function () - { - return this.count() !== 0; - }, - - - concat: __arrayProto.concat, - - - context: [], // array of table settings objects - - - count: function () - { - return this.flatten().length; - }, - - - each: function ( fn ) - { - for ( var i=0, ien=this.length ; i<ien; i++ ) { - fn.call( this, this[i], i, this ); - } - - return this; - }, - - - eq: function ( idx ) - { - var ctx = this.context; - - return ctx.length > idx ? - new _Api( ctx[idx], this[idx] ) : - null; - }, - - - filter: function ( fn ) - { - var a = []; - - if ( __arrayProto.filter ) { - a = __arrayProto.filter.call( this, fn, this ); - } - else { - // Compatibility for browsers without EMCA-252-5 (JS 1.6) - for ( var i=0, ien=this.length ; i<ien ; i++ ) { - if ( fn.call( this, this[i], i, this ) ) { - a.push( this[i] ); - } - } - } - - return new _Api( this.context, a ); - }, - - - flatten: function () - { - var a = []; - return new _Api( this.context, a.concat.apply( a, this.toArray() ) ); - }, - - - join: __arrayProto.join, - - - indexOf: __arrayProto.indexOf || function (obj, start) - { - for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) { - if ( this[i] === obj ) { - return i; - } - } - return -1; - }, - - iterator: function ( flatten, type, fn, alwaysNew ) { - var - a = [], ret, - i, ien, j, jen, - context = this.context, - rows, items, item, - selector = this.selector; - - // Argument shifting - if ( typeof flatten === 'string' ) { - alwaysNew = fn; - fn = type; - type = flatten; - flatten = false; - } - - for ( i=0, ien=context.length ; i<ien ; i++ ) { - var apiInst = new _Api( context[i] ); - - if ( type === 'table' ) { - ret = fn.call( apiInst, context[i], i ); - - if ( ret !== undefined ) { - a.push( ret ); - } - } - else if ( type === 'columns' || type === 'rows' ) { - // this has same length as context - one entry for each table - ret = fn.call( apiInst, context[i], this[i], i ); - - if ( ret !== undefined ) { - a.push( ret ); - } - } - else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) { - // columns and rows share the same structure. - // 'this' is an array of column indexes for each context - items = this[i]; - - if ( type === 'column-rows' ) { - rows = _selector_row_indexes( context[i], selector.opts ); - } - - for ( j=0, jen=items.length ; j<jen ; j++ ) { - item = items[j]; - - if ( type === 'cell' ) { - ret = fn.call( apiInst, context[i], item.row, item.column, i, j ); - } - else { - ret = fn.call( apiInst, context[i], item, i, j, rows ); - } - - if ( ret !== undefined ) { - a.push( ret ); - } - } - } - } - - if ( a.length || alwaysNew ) { - var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a ); - var apiSelector = api.selector; - apiSelector.rows = selector.rows; - apiSelector.cols = selector.cols; - apiSelector.opts = selector.opts; - return api; - } - return this; - }, - - - lastIndexOf: __arrayProto.lastIndexOf || function (obj, start) - { - // Bit cheeky... - return this.indexOf.apply( this.toArray.reverse(), arguments ); - }, - - - length: 0, - - - map: function ( fn ) - { - var a = []; - - if ( __arrayProto.map ) { - a = __arrayProto.map.call( this, fn, this ); - } - else { - // Compatibility for browsers without EMCA-252-5 (JS 1.6) - for ( var i=0, ien=this.length ; i<ien ; i++ ) { - a.push( fn.call( this, this[i], i ) ); - } - } - - return new _Api( this.context, a ); - }, - - - pluck: function ( prop ) - { - return this.map( function ( el ) { - return el[ prop ]; - } ); - }, - - pop: __arrayProto.pop, - - - push: __arrayProto.push, - - - // Does not return an API instance - reduce: __arrayProto.reduce || function ( fn, init ) - { - return _fnReduce( this, fn, init, 0, this.length, 1 ); - }, - - - reduceRight: __arrayProto.reduceRight || function ( fn, init ) - { - return _fnReduce( this, fn, init, this.length-1, -1, -1 ); - }, - - - reverse: __arrayProto.reverse, - - - // Object with rows, columns and opts - selector: null, - - - shift: __arrayProto.shift, - - - slice: function () { - return new _Api( this.context, this ); - }, - - - sort: __arrayProto.sort, // ? name - order? - - - splice: __arrayProto.splice, - - - toArray: function () - { - return __arrayProto.slice.call( this ); - }, - - - to$: function () - { - return $( this ); - }, - - - toJQuery: function () - { - return $( this ); - }, - - - unique: function () - { - return new _Api( this.context, _unique(this) ); - }, - - - unshift: __arrayProto.unshift - } ); - - - _Api.extend = function ( scope, obj, ext ) - { - // Only extend API instances and static properties of the API - if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) { - return; - } - - var - i, ien, - struct, - methodScoping = function ( scope, fn, struc ) { - return function () { - var ret = fn.apply( scope, arguments ); - - // Method extension - _Api.extend( ret, ret, struc.methodExt ); - return ret; - }; - }; - - for ( i=0, ien=ext.length ; i<ien ; i++ ) { - struct = ext[i]; - - // Value - obj[ struct.name ] = struct.type === 'function' ? - methodScoping( scope, struct.val, struct ) : - struct.type === 'object' ? - {} : - struct.val; - - obj[ struct.name ].__dt_wrapper = true; - - // Property extension - _Api.extend( scope, obj[ struct.name ], struct.propExt ); - } - }; - - - // @todo - Is there need for an augment function? - // _Api.augment = function ( inst, name ) - // { - // // Find src object in the structure from the name - // var parts = name.split('.'); - - // _Api.extend( inst, obj ); - // }; - - - // [ - // { - // name: 'data' -- string - Property name - // val: function () {}, -- function - Api method (or undefined if just an object - // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result - // propExt: [ ... ] -- array - Array of Api object definitions to extend the property - // }, - // { - // name: 'row' - // val: {}, - // methodExt: [ ... ], - // propExt: [ - // { - // name: 'data' - // val: function () {}, - // methodExt: [ ... ], - // propExt: [ ... ] - // }, - // ... - // ] - // } - // ] - - _Api.register = _api_register = function ( name, val ) - { - if ( Array.isArray( name ) ) { - for ( var j=0, jen=name.length ; j<jen ; j++ ) { - _Api.register( name[j], val ); - } - return; - } - - var - i, ien, - heir = name.split('.'), - struct = __apiStruct, - key, method; - - var find = function ( src, name ) { - for ( var i=0, ien=src.length ; i<ien ; i++ ) { - if ( src[i].name === name ) { - return src[i]; - } - } - return null; - }; - - for ( i=0, ien=heir.length ; i<ien ; i++ ) { - method = heir[i].indexOf('()') !== -1; - key = method ? - heir[i].replace('()', '') : - heir[i]; - - var src = find( struct, key ); - if ( ! src ) { - src = { - name: key, - val: {}, - methodExt: [], - propExt: [], - type: 'object' - }; - struct.push( src ); - } - - if ( i === ien-1 ) { - src.val = val; - src.type = typeof val === 'function' ? - 'function' : - $.isPlainObject( val ) ? - 'object' : - 'other'; - } - else { - struct = method ? - src.methodExt : - src.propExt; - } - } - }; - - _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) { - _Api.register( pluralName, val ); - - _Api.register( singularName, function () { - var ret = val.apply( this, arguments ); - - if ( ret === this ) { - // Returned item is the API instance that was passed in, return it - return this; - } - else if ( ret instanceof _Api ) { - // New API instance returned, want the value from the first item - // in the returned array for the singular result. - return ret.length ? - Array.isArray( ret[0] ) ? - new _Api( ret.context, ret[0] ) : // Array results are 'enhanced' - ret[0] : - undefined; - } - - // Non-API return - just fire it back - return ret; - } ); - }; - - - /** - * Selector for HTML tables. Apply the given selector to the give array of - * DataTables settings objects. - * - * @param {string|integer} [selector] jQuery selector string or integer - * @param {array} Array of DataTables settings objects to be filtered - * @return {array} - * @ignore - */ - var __table_selector = function ( selector, a ) - { - if ( Array.isArray(selector) ) { - return $.map( selector, function (item) { - return __table_selector(item, a); - } ); - } - - // Integer is used to pick out a table by index - if ( typeof selector === 'number' ) { - return [ a[ selector ] ]; - } - - // Perform a jQuery selector on the table nodes - var nodes = $.map( a, function (el, i) { - return el.nTable; - } ); - - return $(nodes) - .filter( selector ) - .map( function (i) { - // Need to translate back from the table node to the settings - var idx = $.inArray( this, nodes ); - return a[ idx ]; - } ) - .toArray(); - }; - - - - /** - * Context selector for the API's context (i.e. the tables the API instance - * refers to. - * - * @name DataTable.Api#tables - * @param {string|integer} [selector] Selector to pick which tables the iterator - * should operate on. If not given, all tables in the current context are - * used. This can be given as a jQuery selector (for example `':gt(0)'`) to - * select multiple tables or as an integer to select a single table. - * @returns {DataTable.Api} Returns a new API instance if a selector is given. - */ - _api_register( 'tables()', function ( selector ) { - // A new instance is created if there was a selector specified - return selector !== undefined && selector !== null ? - new _Api( __table_selector( selector, this.context ) ) : - this; - } ); - - - _api_register( 'table()', function ( selector ) { - var tables = this.tables( selector ); - var ctx = tables.context; - - // Truncate to the first matched table - return ctx.length ? - new _Api( ctx[0] ) : - tables; - } ); - - - _api_registerPlural( 'tables().nodes()', 'table().node()' , function () { - return this.iterator( 'table', function ( ctx ) { - return ctx.nTable; - }, 1 ); - } ); - - - _api_registerPlural( 'tables().body()', 'table().body()' , function () { - return this.iterator( 'table', function ( ctx ) { - return ctx.nTBody; - }, 1 ); - } ); - - - _api_registerPlural( 'tables().header()', 'table().header()' , function () { - return this.iterator( 'table', function ( ctx ) { - return ctx.nTHead; - }, 1 ); - } ); - - - _api_registerPlural( 'tables().footer()', 'table().footer()' , function () { - return this.iterator( 'table', function ( ctx ) { - return ctx.nTFoot; - }, 1 ); - } ); - - - _api_registerPlural( 'tables().containers()', 'table().container()' , function () { - return this.iterator( 'table', function ( ctx ) { - return ctx.nTableWrapper; - }, 1 ); - } ); - - - - /** - * Redraw the tables in the current context. - */ - _api_register( 'draw()', function ( paging ) { - return this.iterator( 'table', function ( settings ) { - if ( paging === 'page' ) { - _fnDraw( settings ); - } - else { - if ( typeof paging === 'string' ) { - paging = paging === 'full-hold' ? - false : - true; - } - - _fnReDraw( settings, paging===false ); - } - } ); - } ); - - - - /** - * Get the current page index. - * - * @return {integer} Current page index (zero based) - *//** - * Set the current page. - * - * Note that if you attempt to show a page which does not exist, DataTables will - * not throw an error, but rather reset the paging. - * - * @param {integer|string} action The paging action to take. This can be one of: - * * `integer` - The page index to jump to - * * `string` - An action to take: - * * `first` - Jump to first page. - * * `next` - Jump to the next page - * * `previous` - Jump to previous page - * * `last` - Jump to the last page. - * @returns {DataTables.Api} this - */ - _api_register( 'page()', function ( action ) { - if ( action === undefined ) { - return this.page.info().page; // not an expensive call - } - - // else, have an action to take on all tables - return this.iterator( 'table', function ( settings ) { - _fnPageChange( settings, action ); - } ); - } ); - - - /** - * Paging information for the first table in the current context. - * - * If you require paging information for another table, use the `table()` method - * with a suitable selector. - * - * @return {object} Object with the following properties set: - * * `page` - Current page index (zero based - i.e. the first page is `0`) - * * `pages` - Total number of pages - * * `start` - Display index for the first record shown on the current page - * * `end` - Display index for the last record shown on the current page - * * `length` - Display length (number of records). Note that generally `start - * + length = end`, but this is not always true, for example if there are - * only 2 records to show on the final page, with a length of 10. - * * `recordsTotal` - Full data set length - * * `recordsDisplay` - Data set length once the current filtering criterion - * are applied. - */ - _api_register( 'page.info()', function ( action ) { - if ( this.context.length === 0 ) { - return undefined; - } - - var - settings = this.context[0], - start = settings._iDisplayStart, - len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1, - visRecords = settings.fnRecordsDisplay(), - all = len === -1; - - return { - "page": all ? 0 : Math.floor( start / len ), - "pages": all ? 1 : Math.ceil( visRecords / len ), - "start": start, - "end": settings.fnDisplayEnd(), - "length": len, - "recordsTotal": settings.fnRecordsTotal(), - "recordsDisplay": visRecords, - "serverSide": _fnDataSource( settings ) === 'ssp' - }; - } ); - - - /** - * Get the current page length. - * - * @return {integer} Current page length. Note `-1` indicates that all records - * are to be shown. - *//** - * Set the current page length. - * - * @param {integer} Page length to set. Use `-1` to show all records. - * @returns {DataTables.Api} this - */ - _api_register( 'page.len()', function ( len ) { - // Note that we can't call this function 'length()' because `length` - // is a Javascript property of functions which defines how many arguments - // the function expects. - if ( len === undefined ) { - return this.context.length !== 0 ? - this.context[0]._iDisplayLength : - undefined; - } - - // else, set the page length - return this.iterator( 'table', function ( settings ) { - _fnLengthChange( settings, len ); - } ); - } ); - - - - var __reload = function ( settings, holdPosition, callback ) { - // Use the draw event to trigger a callback - if ( callback ) { - var api = new _Api( settings ); - - api.one( 'draw', function () { - callback( api.ajax.json() ); - } ); - } - - if ( _fnDataSource( settings ) == 'ssp' ) { - _fnReDraw( settings, holdPosition ); - } - else { - _fnProcessingDisplay( settings, true ); - - // Cancel an existing request - var xhr = settings.jqXHR; - if ( xhr && xhr.readyState !== 4 ) { - xhr.abort(); - } - - // Trigger xhr - _fnBuildAjax( settings, [], function( json ) { - _fnClearTable( settings ); - - var data = _fnAjaxDataSrc( settings, json ); - for ( var i=0, ien=data.length ; i<ien ; i++ ) { - _fnAddData( settings, data[i] ); - } - - _fnReDraw( settings, holdPosition ); - _fnProcessingDisplay( settings, false ); - } ); - } - }; - - - /** - * Get the JSON response from the last Ajax request that DataTables made to the - * server. Note that this returns the JSON from the first table in the current - * context. - * - * @return {object} JSON received from the server. - */ - _api_register( 'ajax.json()', function () { - var ctx = this.context; - - if ( ctx.length > 0 ) { - return ctx[0].json; - } - - // else return undefined; - } ); - - - /** - * Get the data submitted in the last Ajax request - */ - _api_register( 'ajax.params()', function () { - var ctx = this.context; - - if ( ctx.length > 0 ) { - return ctx[0].oAjaxData; - } - - // else return undefined; - } ); - - - /** - * Reload tables from the Ajax data source. Note that this function will - * automatically re-draw the table when the remote data has been loaded. - * - * @param {boolean} [reset=true] Reset (default) or hold the current paging - * position. A full re-sort and re-filter is performed when this method is - * called, which is why the pagination reset is the default action. - * @returns {DataTables.Api} this - */ - _api_register( 'ajax.reload()', function ( callback, resetPaging ) { - return this.iterator( 'table', function (settings) { - __reload( settings, resetPaging===false, callback ); - } ); - } ); - - - /** - * Get the current Ajax URL. Note that this returns the URL from the first - * table in the current context. - * - * @return {string} Current Ajax source URL - *//** - * Set the Ajax URL. Note that this will set the URL for all tables in the - * current context. - * - * @param {string} url URL to set. - * @returns {DataTables.Api} this - */ - _api_register( 'ajax.url()', function ( url ) { - var ctx = this.context; - - if ( url === undefined ) { - // get - if ( ctx.length === 0 ) { - return undefined; - } - ctx = ctx[0]; - - return ctx.ajax ? - $.isPlainObject( ctx.ajax ) ? - ctx.ajax.url : - ctx.ajax : - ctx.sAjaxSource; - } - - // set - return this.iterator( 'table', function ( settings ) { - if ( $.isPlainObject( settings.ajax ) ) { - settings.ajax.url = url; - } - else { - settings.ajax = url; - } - // No need to consider sAjaxSource here since DataTables gives priority - // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any - // value of `sAjaxSource` redundant. - } ); - } ); - - - /** - * Load data from the newly set Ajax URL. Note that this method is only - * available when `ajax.url()` is used to set a URL. Additionally, this method - * has the same effect as calling `ajax.reload()` but is provided for - * convenience when setting a new URL. Like `ajax.reload()` it will - * automatically redraw the table once the remote data has been loaded. - * - * @returns {DataTables.Api} this - */ - _api_register( 'ajax.url().load()', function ( callback, resetPaging ) { - // Same as a reload, but makes sense to present it for easy access after a - // url change - return this.iterator( 'table', function ( ctx ) { - __reload( ctx, resetPaging===false, callback ); - } ); - } ); - - - - - var _selector_run = function ( type, selector, selectFn, settings, opts ) - { - var - out = [], res, - a, i, ien, j, jen, - selectorType = typeof selector; - - // Can't just check for isArray here, as an API or jQuery instance might be - // given with their array like look - if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) { - selector = [ selector ]; - } - - for ( i=0, ien=selector.length ; i<ien ; i++ ) { - // Only split on simple strings - complex expressions will be jQuery selectors - a = selector[i] && selector[i].split && ! selector[i].match(/[\[\(:]/) ? - selector[i].split(',') : - [ selector[i] ]; - - for ( j=0, jen=a.length ; j<jen ; j++ ) { - res = selectFn( typeof a[j] === 'string' ? (a[j]).trim() : a[j] ); - - if ( res && res.length ) { - out = out.concat( res ); - } - } - } - - // selector extensions - var ext = _ext.selector[ type ]; - if ( ext.length ) { - for ( i=0, ien=ext.length ; i<ien ; i++ ) { - out = ext[i]( settings, opts, out ); - } - } - - return _unique( out ); - }; - - - var _selector_opts = function ( opts ) - { - if ( ! opts ) { - opts = {}; - } - - // Backwards compatibility for 1.9- which used the terminology filter rather - // than search - if ( opts.filter && opts.search === undefined ) { - opts.search = opts.filter; - } - - return $.extend( { - search: 'none', - order: 'current', - page: 'all' - }, opts ); - }; - - - var _selector_first = function ( inst ) - { - // Reduce the API instance to the first item found - for ( var i=0, ien=inst.length ; i<ien ; i++ ) { - if ( inst[i].length > 0 ) { - // Assign the first element to the first item in the instance - // and truncate the instance and context - inst[0] = inst[i]; - inst[0].length = 1; - inst.length = 1; - inst.context = [ inst.context[i] ]; - - return inst; - } - } - - // Not found - return an empty instance - inst.length = 0; - return inst; - }; - - - var _selector_row_indexes = function ( settings, opts ) - { - var - i, ien, tmp, a=[], - displayFiltered = settings.aiDisplay, - displayMaster = settings.aiDisplayMaster; - - var - search = opts.search, // none, applied, removed - order = opts.order, // applied, current, index (original - compatibility with 1.9) - page = opts.page; // all, current - - if ( _fnDataSource( settings ) == 'ssp' ) { - // In server-side processing mode, most options are irrelevant since - // rows not shown don't exist and the index order is the applied order - // Removed is a special case - for consistency just return an empty - // array - return search === 'removed' ? - [] : - _range( 0, displayMaster.length ); - } - else if ( page == 'current' ) { - // Current page implies that order=current and fitler=applied, since it is - // fairly senseless otherwise, regardless of what order and search actually - // are - for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) { - a.push( displayFiltered[i] ); - } - } - else if ( order == 'current' || order == 'applied' ) { - if ( search == 'none') { - a = displayMaster.slice(); - } - else if ( search == 'applied' ) { - a = displayFiltered.slice(); - } - else if ( search == 'removed' ) { - // O(n+m) solution by creating a hash map - var displayFilteredMap = {}; - - for ( var i=0, ien=displayFiltered.length ; i<ien ; i++ ) { - displayFilteredMap[displayFiltered[i]] = null; - } - - a = $.map( displayMaster, function (el) { - return ! displayFilteredMap.hasOwnProperty(el) ? - el : - null; - } ); - } - } - else if ( order == 'index' || order == 'original' ) { - for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { - if ( search == 'none' ) { - a.push( i ); - } - else { // applied | removed - tmp = $.inArray( i, displayFiltered ); - - if ((tmp === -1 && search == 'removed') || - (tmp >= 0 && search == 'applied') ) - { - a.push( i ); - } - } - } - } - - return a; - }; - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Rows - * - * {} - no selector - use all available rows - * {integer} - row aoData index - * {node} - TR node - * {string} - jQuery selector to apply to the TR elements - * {array} - jQuery array of nodes, or simply an array of TR nodes - * - */ - var __row_selector = function ( settings, selector, opts ) - { - var rows; - var run = function ( sel ) { - var selInt = _intVal( sel ); - var i, ien; - var aoData = settings.aoData; - - // Short cut - selector is a number and no options provided (default is - // all records, so no need to check if the index is in there, since it - // must be - dev error if the index doesn't exist). - if ( selInt !== null && ! opts ) { - return [ selInt ]; - } - - if ( ! rows ) { - rows = _selector_row_indexes( settings, opts ); - } - - if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) { - // Selector - integer - return [ selInt ]; - } - else if ( sel === null || sel === undefined || sel === '' ) { - // Selector - none - return rows; - } - - // Selector - function - if ( typeof sel === 'function' ) { - return $.map( rows, function (idx) { - var row = aoData[ idx ]; - return sel( idx, row._aData, row.nTr ) ? idx : null; - } ); - } - - // Selector - node - if ( sel.nodeName ) { - var rowIdx = sel._DT_RowIndex; // Property added by DT for fast lookup - var cellIdx = sel._DT_CellIndex; - - if ( rowIdx !== undefined ) { - // Make sure that the row is actually still present in the table - return aoData[ rowIdx ] && aoData[ rowIdx ].nTr === sel ? - [ rowIdx ] : - []; - } - else if ( cellIdx ) { - return aoData[ cellIdx.row ] && aoData[ cellIdx.row ].nTr === sel.parentNode ? - [ cellIdx.row ] : - []; - } - else { - var host = $(sel).closest('*[data-dt-row]'); - return host.length ? - [ host.data('dt-row') ] : - []; - } - } - - // ID selector. Want to always be able to select rows by id, regardless - // of if the tr element has been created or not, so can't rely upon - // jQuery here - hence a custom implementation. This does not match - // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, - // but to select it using a CSS selector engine (like Sizzle or - // querySelect) it would need to need to be escaped for some characters. - // DataTables simplifies this for row selectors since you can select - // only a row. A # indicates an id any anything that follows is the id - - // unescaped. - if ( typeof sel === 'string' && sel.charAt(0) === '#' ) { - // get row index from id - var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ]; - if ( rowObj !== undefined ) { - return [ rowObj.idx ]; - } - - // need to fall through to jQuery in case there is DOM id that - // matches - } - - // Get nodes in the order from the `rows` array with null values removed - var nodes = _removeEmpty( - _pluck_order( settings.aoData, rows, 'nTr' ) - ); - - // Selector - jQuery selector string, array of nodes or jQuery object/ - // As jQuery's .filter() allows jQuery objects to be passed in filter, - // it also allows arrays, so this will cope with all three options - return $(nodes) - .filter( sel ) - .map( function () { - return this._DT_RowIndex; - } ) - .toArray(); - }; - - return _selector_run( 'row', selector, run, settings, opts ); - }; - - - _api_register( 'rows()', function ( selector, opts ) { - // argument shifting - if ( selector === undefined ) { - selector = ''; - } - else if ( $.isPlainObject( selector ) ) { - opts = selector; - selector = ''; - } - - opts = _selector_opts( opts ); - - var inst = this.iterator( 'table', function ( settings ) { - return __row_selector( settings, selector, opts ); - }, 1 ); - - // Want argument shifting here and in __row_selector? - inst.selector.rows = selector; - inst.selector.opts = opts; - - return inst; - } ); - - _api_register( 'rows().nodes()', function () { - return this.iterator( 'row', function ( settings, row ) { - return settings.aoData[ row ].nTr || undefined; - }, 1 ); - } ); - - _api_register( 'rows().data()', function () { - return this.iterator( true, 'rows', function ( settings, rows ) { - return _pluck_order( settings.aoData, rows, '_aData' ); - }, 1 ); - } ); - - _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { - return this.iterator( 'row', function ( settings, row ) { - var r = settings.aoData[ row ]; - return type === 'search' ? r._aFilterData : r._aSortData; - }, 1 ); - } ); - - _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { - return this.iterator( 'row', function ( settings, row ) { - _fnInvalidate( settings, row, src ); - } ); - } ); - - _api_registerPlural( 'rows().indexes()', 'row().index()', function () { - return this.iterator( 'row', function ( settings, row ) { - return row; - }, 1 ); - } ); - - _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) { - var a = []; - var context = this.context; - - // `iterator` will drop undefined values, but in this case we want them - for ( var i=0, ien=context.length ; i<ien ; i++ ) { - for ( var j=0, jen=this[i].length ; j<jen ; j++ ) { - var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData ); - a.push( (hash === true ? '#' : '' )+ id ); - } - } - - return new _Api( context, a ); - } ); - - _api_registerPlural( 'rows().remove()', 'row().remove()', function () { - var that = this; - - this.iterator( 'row', function ( settings, row, thatIdx ) { - var data = settings.aoData; - var rowData = data[ row ]; - var i, ien, j, jen; - var loopRow, loopCells; - - data.splice( row, 1 ); - - // Update the cached indexes - for ( i=0, ien=data.length ; i<ien ; i++ ) { - loopRow = data[i]; - loopCells = loopRow.anCells; - - // Rows - if ( loopRow.nTr !== null ) { - loopRow.nTr._DT_RowIndex = i; - } - - // Cells - if ( loopCells !== null ) { - for ( j=0, jen=loopCells.length ; j<jen ; j++ ) { - loopCells[j]._DT_CellIndex.row = i; - } - } - } - - // Delete from the display arrays - _fnDeleteIndex( settings.aiDisplayMaster, row ); - _fnDeleteIndex( settings.aiDisplay, row ); - _fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes - - // For server-side processing tables - subtract the deleted row from the count - if ( settings._iRecordsDisplay > 0 ) { - settings._iRecordsDisplay--; - } - - // Check for an 'overflow' they case for displaying the table - _fnLengthOverflow( settings ); - - // Remove the row's ID reference if there is one - var id = settings.rowIdFn( rowData._aData ); - if ( id !== undefined ) { - delete settings.aIds[ id ]; - } - } ); - - this.iterator( 'table', function ( settings ) { - for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { - settings.aoData[i].idx = i; - } - } ); - - return this; - } ); - - - _api_register( 'rows.add()', function ( rows ) { - var newRows = this.iterator( 'table', function ( settings ) { - var row, i, ien; - var out = []; - - for ( i=0, ien=rows.length ; i<ien ; i++ ) { - row = rows[i]; - - if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { - out.push( _fnAddTr( settings, row )[0] ); - } - else { - out.push( _fnAddData( settings, row ) ); - } - } - - return out; - }, 1 ); - - // Return an Api.rows() extended instance, so rows().nodes() etc can be used - var modRows = this.rows( -1 ); - modRows.pop(); - $.merge( modRows, newRows ); - - return modRows; - } ); - - - - - - /** - * - */ - _api_register( 'row()', function ( selector, opts ) { - return _selector_first( this.rows( selector, opts ) ); - } ); - - - _api_register( 'row().data()', function ( data ) { - var ctx = this.context; - - if ( data === undefined ) { - // Get - return ctx.length && this.length ? - ctx[0].aoData[ this[0] ]._aData : - undefined; - } - - // Set - var row = ctx[0].aoData[ this[0] ]; - row._aData = data; - - // If the DOM has an id, and the data source is an array - if ( Array.isArray( data ) && row.nTr && row.nTr.id ) { - _fnSetObjectDataFn( ctx[0].rowId )( data, row.nTr.id ); - } - - // Automatically invalidate - _fnInvalidate( ctx[0], this[0], 'data' ); - - return this; - } ); - - - _api_register( 'row().node()', function () { - var ctx = this.context; - - return ctx.length && this.length ? - ctx[0].aoData[ this[0] ].nTr || null : - null; - } ); - - - _api_register( 'row.add()', function ( row ) { - // Allow a jQuery object to be passed in - only a single row is added from - // it though - the first element in the set - if ( row instanceof $ && row.length ) { - row = row[0]; - } - - var rows = this.iterator( 'table', function ( settings ) { - if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { - return _fnAddTr( settings, row )[0]; - } - return _fnAddData( settings, row ); - } ); - - // Return an Api.rows() extended instance, with the newly added row selected - return this.row( rows[0] ); - } ); - - - - var __details_add = function ( ctx, row, data, klass ) - { - // Convert to array of TR elements - var rows = []; - var addRow = function ( r, k ) { - // Recursion to allow for arrays of jQuery objects - if ( Array.isArray( r ) || r instanceof $ ) { - for ( var i=0, ien=r.length ; i<ien ; i++ ) { - addRow( r[i], k ); - } - return; - } - - // If we get a TR element, then just add it directly - up to the dev - // to add the correct number of columns etc - if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) { - rows.push( r ); - } - else { - // Otherwise create a row with a wrapper - var created = $('<tr><td></td></tr>').addClass( k ); - $('td', created) - .addClass( k ) - .html( r ) - [0].colSpan = _fnVisbleColumns( ctx ); - - rows.push( created[0] ); - } - }; - - addRow( data, klass ); - - if ( row._details ) { - row._details.detach(); - } - - row._details = $(rows); - - // If the children were already shown, that state should be retained - if ( row._detailsShow ) { - row._details.insertAfter( row.nTr ); - } - }; - - - var __details_remove = function ( api, idx ) - { - var ctx = api.context; - - if ( ctx.length ) { - var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ]; - - if ( row && row._details ) { - row._details.remove(); - - row._detailsShow = undefined; - row._details = undefined; - } - } - }; - - - var __details_display = function ( api, show ) { - var ctx = api.context; - - if ( ctx.length && api.length ) { - var row = ctx[0].aoData[ api[0] ]; - - if ( row._details ) { - row._detailsShow = show; - - if ( show ) { - row._details.insertAfter( row.nTr ); - } - else { - row._details.detach(); - } - - __details_events( ctx[0] ); - } - } - }; - - - var __details_events = function ( settings ) - { - var api = new _Api( settings ); - var namespace = '.dt.DT_details'; - var drawEvent = 'draw'+namespace; - var colvisEvent = 'column-visibility'+namespace; - var destroyEvent = 'destroy'+namespace; - var data = settings.aoData; - - api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent ); - - if ( _pluck( data, '_details' ).length > 0 ) { - // On each draw, insert the required elements into the document - api.on( drawEvent, function ( e, ctx ) { - if ( settings !== ctx ) { - return; - } - - api.rows( {page:'current'} ).eq(0).each( function (idx) { - // Internal data grab - var row = data[ idx ]; - - if ( row._detailsShow ) { - row._details.insertAfter( row.nTr ); - } - } ); - } ); - - // Column visibility change - update the colspan - api.on( colvisEvent, function ( e, ctx, idx, vis ) { - if ( settings !== ctx ) { - return; - } - - // Update the colspan for the details rows (note, only if it already has - // a colspan) - var row, visible = _fnVisbleColumns( ctx ); - - for ( var i=0, ien=data.length ; i<ien ; i++ ) { - row = data[i]; - - if ( row._details ) { - row._details.children('td[colspan]').attr('colspan', visible ); - } - } - } ); - - // Table destroyed - nuke any child rows - api.on( destroyEvent, function ( e, ctx ) { - if ( settings !== ctx ) { - return; - } - - for ( var i=0, ien=data.length ; i<ien ; i++ ) { - if ( data[i]._details ) { - __details_remove( api, i ); - } - } - } ); - } - }; - - // Strings for the method names to help minification - var _emp = ''; - var _child_obj = _emp+'row().child'; - var _child_mth = _child_obj+'()'; - - // data can be: - // tr - // string - // jQuery or array of any of the above - _api_register( _child_mth, function ( data, klass ) { - var ctx = this.context; - - if ( data === undefined ) { - // get - return ctx.length && this.length ? - ctx[0].aoData[ this[0] ]._details : - undefined; - } - else if ( data === true ) { - // show - this.child.show(); - } - else if ( data === false ) { - // remove - __details_remove( this ); - } - else if ( ctx.length && this.length ) { - // set - __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass ); - } - - return this; - } ); - - - _api_register( [ - _child_obj+'.show()', - _child_mth+'.show()' // only when `child()` was called with parameters (without - ], function ( show ) { // it returns an object and this method is not executed) - __details_display( this, true ); - return this; - } ); - - - _api_register( [ - _child_obj+'.hide()', - _child_mth+'.hide()' // only when `child()` was called with parameters (without - ], function () { // it returns an object and this method is not executed) - __details_display( this, false ); - return this; - } ); - - - _api_register( [ - _child_obj+'.remove()', - _child_mth+'.remove()' // only when `child()` was called with parameters (without - ], function () { // it returns an object and this method is not executed) - __details_remove( this ); - return this; - } ); - - - _api_register( _child_obj+'.isShown()', function () { - var ctx = this.context; - - if ( ctx.length && this.length ) { - // _detailsShown as false or undefined will fall through to return false - return ctx[0].aoData[ this[0] ]._detailsShow || false; - } - return false; - } ); - - - - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Columns - * - * {integer} - column index (>=0 count from left, <0 count from right) - * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) - * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) - * "{string}:name" - column name - * "{string}" - jQuery selector on column header nodes - * - */ - - // can be an array of these items, comma separated list, or an array of comma - // separated lists - - var __re_column_selector = /^([^:]+):(name|visIdx|visible)$/; - - - // r1 and r2 are redundant - but it means that the parameters match for the - // iterator callback in columns().data() - var __columnData = function ( settings, column, r1, r2, rows ) { - var a = []; - for ( var row=0, ien=rows.length ; row<ien ; row++ ) { - a.push( _fnGetCellData( settings, rows[row], column ) ); - } - return a; - }; - - - var __column_selector = function ( settings, selector, opts ) - { - var - columns = settings.aoColumns, - names = _pluck( columns, 'sName' ), - nodes = _pluck( columns, 'nTh' ); - - var run = function ( s ) { - var selInt = _intVal( s ); - - // Selector - all - if ( s === '' ) { - return _range( columns.length ); - } - - // Selector - index - if ( selInt !== null ) { - return [ selInt >= 0 ? - selInt : // Count from left - columns.length + selInt // Count from right (+ because its a negative value) - ]; - } - - // Selector = function - if ( typeof s === 'function' ) { - var rows = _selector_row_indexes( settings, opts ); - - return $.map( columns, function (col, idx) { - return s( - idx, - __columnData( settings, idx, 0, 0, rows ), - nodes[ idx ] - ) ? idx : null; - } ); - } - - // jQuery or string selector - var match = typeof s === 'string' ? - s.match( __re_column_selector ) : - ''; - - if ( match ) { - switch( match[2] ) { - case 'visIdx': - case 'visible': - var idx = parseInt( match[1], 10 ); - // Visible index given, convert to column index - if ( idx < 0 ) { - // Counting from the right - var visColumns = $.map( columns, function (col,i) { - return col.bVisible ? i : null; - } ); - return [ visColumns[ visColumns.length + idx ] ]; - } - // Counting from the left - return [ _fnVisibleToColumnIndex( settings, idx ) ]; - - case 'name': - // match by name. `names` is column index complete and in order - return $.map( names, function (name, i) { - return name === match[1] ? i : null; - } ); - - default: - return []; - } - } - - // Cell in the table body - if ( s.nodeName && s._DT_CellIndex ) { - return [ s._DT_CellIndex.column ]; - } - - // jQuery selector on the TH elements for the columns - var jqResult = $( nodes ) - .filter( s ) - .map( function () { - return $.inArray( this, nodes ); // `nodes` is column index complete and in order - } ) - .toArray(); - - if ( jqResult.length || ! s.nodeName ) { - return jqResult; - } - - // Otherwise a node which might have a `dt-column` data attribute, or be - // a child or such an element - var host = $(s).closest('*[data-dt-column]'); - return host.length ? - [ host.data('dt-column') ] : - []; - }; - - return _selector_run( 'column', selector, run, settings, opts ); - }; - - - var __setColumnVis = function ( settings, column, vis ) { - var - cols = settings.aoColumns, - col = cols[ column ], - data = settings.aoData, - row, cells, i, ien, tr; - - // Get - if ( vis === undefined ) { - return col.bVisible; - } - - // Set - // No change - if ( col.bVisible === vis ) { - return; - } - - if ( vis ) { - // Insert column - // Need to decide if we should use appendChild or insertBefore - var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 ); - - for ( i=0, ien=data.length ; i<ien ; i++ ) { - tr = data[i].nTr; - cells = data[i].anCells; - - if ( tr ) { - // insertBefore can act like appendChild if 2nd arg is null - tr.insertBefore( cells[ column ], cells[ insertBefore ] || null ); - } - } - } - else { - // Remove column - $( _pluck( settings.aoData, 'anCells', column ) ).detach(); - } - - // Common actions - col.bVisible = vis; - }; - - - _api_register( 'columns()', function ( selector, opts ) { - // argument shifting - if ( selector === undefined ) { - selector = ''; - } - else if ( $.isPlainObject( selector ) ) { - opts = selector; - selector = ''; - } - - opts = _selector_opts( opts ); - - var inst = this.iterator( 'table', function ( settings ) { - return __column_selector( settings, selector, opts ); - }, 1 ); - - // Want argument shifting here and in _row_selector? - inst.selector.cols = selector; - inst.selector.opts = opts; - - return inst; - } ); - - _api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) { - return this.iterator( 'column', function ( settings, column ) { - return settings.aoColumns[column].nTh; - }, 1 ); - } ); - - _api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) { - return this.iterator( 'column', function ( settings, column ) { - return settings.aoColumns[column].nTf; - }, 1 ); - } ); - - _api_registerPlural( 'columns().data()', 'column().data()', function () { - return this.iterator( 'column-rows', __columnData, 1 ); - } ); - - _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () { - return this.iterator( 'column', function ( settings, column ) { - return settings.aoColumns[column].mData; - }, 1 ); - } ); - - _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) { - return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { - return _pluck_order( settings.aoData, rows, - type === 'search' ? '_aFilterData' : '_aSortData', column - ); - }, 1 ); - } ); - - _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { - return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { - return _pluck_order( settings.aoData, rows, 'anCells', column ) ; - }, 1 ); - } ); - - _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) { - var that = this; - var ret = this.iterator( 'column', function ( settings, column ) { - if ( vis === undefined ) { - return settings.aoColumns[ column ].bVisible; - } // else - __setColumnVis( settings, column, vis ); - } ); - - // Group the column visibility changes - if ( vis !== undefined ) { - this.iterator( 'table', function ( settings ) { - // Redraw the header after changes - _fnDrawHead( settings, settings.aoHeader ); - _fnDrawHead( settings, settings.aoFooter ); - - // Update colspan for no records display. Child rows and extensions will use their own - // listeners to do this - only need to update the empty table item here - if ( ! settings.aiDisplay.length ) { - $(settings.nTBody).find('td[colspan]').attr('colspan', _fnVisbleColumns(settings)); - } - - _fnSaveState( settings ); - - // Second loop once the first is done for events - that.iterator( 'column', function ( settings, column ) { - _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] ); - } ); - - if ( calc === undefined || calc ) { - that.columns.adjust(); - } - }); - } - - return ret; - } ); - - _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) { - return this.iterator( 'column', function ( settings, column ) { - return type === 'visible' ? - _fnColumnIndexToVisible( settings, column ) : - column; - }, 1 ); - } ); - - _api_register( 'columns.adjust()', function () { - return this.iterator( 'table', function ( settings ) { - _fnAdjustColumnSizing( settings ); - }, 1 ); - } ); - - _api_register( 'column.index()', function ( type, idx ) { - if ( this.context.length !== 0 ) { - var ctx = this.context[0]; - - if ( type === 'fromVisible' || type === 'toData' ) { - return _fnVisibleToColumnIndex( ctx, idx ); - } - else if ( type === 'fromData' || type === 'toVisible' ) { - return _fnColumnIndexToVisible( ctx, idx ); - } - } - } ); - - _api_register( 'column()', function ( selector, opts ) { - return _selector_first( this.columns( selector, opts ) ); - } ); - - var __cell_selector = function ( settings, selector, opts ) - { - var data = settings.aoData; - var rows = _selector_row_indexes( settings, opts ); - var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) ); - var allCells = $(_flatten( [], cells )); - var row; - var columns = settings.aoColumns.length; - var a, i, ien, j, o, host; - - var run = function ( s ) { - var fnSelector = typeof s === 'function'; - - if ( s === null || s === undefined || fnSelector ) { - // All cells and function selectors - a = []; - - for ( i=0, ien=rows.length ; i<ien ; i++ ) { - row = rows[i]; - - for ( j=0 ; j<columns ; j++ ) { - o = { - row: row, - column: j - }; - - if ( fnSelector ) { - // Selector - function - host = data[ row ]; - - if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) { - a.push( o ); - } - } - else { - // Selector - all - a.push( o ); - } - } - } - - return a; - } - - // Selector - index - if ( $.isPlainObject( s ) ) { - // Valid cell index and its in the array of selectable rows - return s.column !== undefined && s.row !== undefined && $.inArray( s.row, rows ) !== -1 ? - [s] : - []; - } - - // Selector - jQuery filtered cells - var jqResult = allCells - .filter( s ) - .map( function (i, el) { - return { // use a new object, in case someone changes the values - row: el._DT_CellIndex.row, - column: el._DT_CellIndex.column - }; - } ) - .toArray(); - - if ( jqResult.length || ! s.nodeName ) { - return jqResult; - } - - // Otherwise the selector is a node, and there is one last option - the - // element might be a child of an element which has dt-row and dt-column - // data attributes - host = $(s).closest('*[data-dt-row]'); - return host.length ? - [ { - row: host.data('dt-row'), - column: host.data('dt-column') - } ] : - []; - }; - - return _selector_run( 'cell', selector, run, settings, opts ); - }; - - - - - _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) { - // Argument shifting - if ( $.isPlainObject( rowSelector ) ) { - // Indexes - if ( rowSelector.row === undefined ) { - // Selector options in first parameter - opts = rowSelector; - rowSelector = null; - } - else { - // Cell index objects in first parameter - opts = columnSelector; - columnSelector = null; - } - } - if ( $.isPlainObject( columnSelector ) ) { - opts = columnSelector; - columnSelector = null; - } - - // Cell selector - if ( columnSelector === null || columnSelector === undefined ) { - return this.iterator( 'table', function ( settings ) { - return __cell_selector( settings, rowSelector, _selector_opts( opts ) ); - } ); - } - - // The default built in options need to apply to row and columns - var internalOpts = opts ? { - page: opts.page, - order: opts.order, - search: opts.search - } : {}; - - // Row + column selector - var columns = this.columns( columnSelector, internalOpts ); - var rows = this.rows( rowSelector, internalOpts ); - var i, ien, j, jen; - - var cellsNoOpts = this.iterator( 'table', function ( settings, idx ) { - var a = []; - - for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) { - for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) { - a.push( { - row: rows[idx][i], - column: columns[idx][j] - } ); - } - } - - return a; - }, 1 ); - - // There is currently only one extension which uses a cell selector extension - // It is a _major_ performance drag to run this if it isn't needed, so this is - // an extension specific check at the moment - var cells = opts && opts.selected ? - this.cells( cellsNoOpts, opts ) : - cellsNoOpts; - - $.extend( cells.selector, { - cols: columnSelector, - rows: rowSelector, - opts: opts - } ); - - return cells; - } ); - - - _api_registerPlural( 'cells().nodes()', 'cell().node()', function () { - return this.iterator( 'cell', function ( settings, row, column ) { - var data = settings.aoData[ row ]; - - return data && data.anCells ? - data.anCells[ column ] : - undefined; - }, 1 ); - } ); - - - _api_register( 'cells().data()', function () { - return this.iterator( 'cell', function ( settings, row, column ) { - return _fnGetCellData( settings, row, column ); - }, 1 ); - } ); - - - _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) { - type = type === 'search' ? '_aFilterData' : '_aSortData'; - - return this.iterator( 'cell', function ( settings, row, column ) { - return settings.aoData[ row ][ type ][ column ]; - }, 1 ); - } ); - - - _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) { - return this.iterator( 'cell', function ( settings, row, column ) { - return _fnGetCellData( settings, row, column, type ); - }, 1 ); - } ); - - - _api_registerPlural( 'cells().indexes()', 'cell().index()', function () { - return this.iterator( 'cell', function ( settings, row, column ) { - return { - row: row, - column: column, - columnVisible: _fnColumnIndexToVisible( settings, column ) - }; - }, 1 ); - } ); - - - _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) { - return this.iterator( 'cell', function ( settings, row, column ) { - _fnInvalidate( settings, row, src, column ); - } ); - } ); - - - - _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) { - return _selector_first( this.cells( rowSelector, columnSelector, opts ) ); - } ); - - - _api_register( 'cell().data()', function ( data ) { - var ctx = this.context; - var cell = this[0]; - - if ( data === undefined ) { - // Get - return ctx.length && cell.length ? - _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) : - undefined; - } - - // Set - _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data ); - _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column ); - - return this; - } ); - - - - /** - * Get current ordering (sorting) that has been applied to the table. - * - * @returns {array} 2D array containing the sorting information for the first - * table in the current context. Each element in the parent array represents - * a column being sorted upon (i.e. multi-sorting with two columns would have - * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is - * the column index that the sorting condition applies to, the second is the - * direction of the sort (`desc` or `asc`) and, optionally, the third is the - * index of the sorting order from the `column.sorting` initialisation array. - *//** - * Set the ordering for the table. - * - * @param {integer} order Column index to sort upon. - * @param {string} direction Direction of the sort to be applied (`asc` or `desc`) - * @returns {DataTables.Api} this - *//** - * Set the ordering for the table. - * - * @param {array} order 1D array of sorting information to be applied. - * @param {array} [...] Optional additional sorting conditions - * @returns {DataTables.Api} this - *//** - * Set the ordering for the table. - * - * @param {array} order 2D array of sorting information to be applied. - * @returns {DataTables.Api} this - */ - _api_register( 'order()', function ( order, dir ) { - var ctx = this.context; - - if ( order === undefined ) { - // get - return ctx.length !== 0 ? - ctx[0].aaSorting : - undefined; - } - - // set - if ( typeof order === 'number' ) { - // Simple column / direction passed in - order = [ [ order, dir ] ]; - } - else if ( order.length && ! Array.isArray( order[0] ) ) { - // Arguments passed in (list of 1D arrays) - order = Array.prototype.slice.call( arguments ); - } - // otherwise a 2D array was passed in - - return this.iterator( 'table', function ( settings ) { - settings.aaSorting = order.slice(); - } ); - } ); - - - /** - * Attach a sort listener to an element for a given column - * - * @param {node|jQuery|string} node Identifier for the element(s) to attach the - * listener to. This can take the form of a single DOM node, a jQuery - * collection of nodes or a jQuery selector which will identify the node(s). - * @param {integer} column the column that a click on this node will sort on - * @param {function} [callback] callback function when sort is run - * @returns {DataTables.Api} this - */ - _api_register( 'order.listener()', function ( node, column, callback ) { - return this.iterator( 'table', function ( settings ) { - _fnSortAttachListener( settings, node, column, callback ); - } ); - } ); - - - _api_register( 'order.fixed()', function ( set ) { - if ( ! set ) { - var ctx = this.context; - var fixed = ctx.length ? - ctx[0].aaSortingFixed : - undefined; - - return Array.isArray( fixed ) ? - { pre: fixed } : - fixed; - } - - return this.iterator( 'table', function ( settings ) { - settings.aaSortingFixed = $.extend( true, {}, set ); - } ); - } ); - - - // Order by the selected column(s) - _api_register( [ - 'columns().order()', - 'column().order()' - ], function ( dir ) { - var that = this; - - return this.iterator( 'table', function ( settings, i ) { - var sort = []; - - $.each( that[i], function (j, col) { - sort.push( [ col, dir ] ); - } ); - - settings.aaSorting = sort; - } ); - } ); - - - - _api_register( 'search()', function ( input, regex, smart, caseInsen ) { - var ctx = this.context; - - if ( input === undefined ) { - // get - return ctx.length !== 0 ? - ctx[0].oPreviousSearch.sSearch : - undefined; - } - - // set - return this.iterator( 'table', function ( settings ) { - if ( ! settings.oFeatures.bFilter ) { - return; - } - - _fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, { - "sSearch": input+"", - "bRegex": regex === null ? false : regex, - "bSmart": smart === null ? true : smart, - "bCaseInsensitive": caseInsen === null ? true : caseInsen - } ), 1 ); - } ); - } ); - - - _api_registerPlural( - 'columns().search()', - 'column().search()', - function ( input, regex, smart, caseInsen ) { - return this.iterator( 'column', function ( settings, column ) { - var preSearch = settings.aoPreSearchCols; - - if ( input === undefined ) { - // get - return preSearch[ column ].sSearch; - } - - // set - if ( ! settings.oFeatures.bFilter ) { - return; - } - - $.extend( preSearch[ column ], { - "sSearch": input+"", - "bRegex": regex === null ? false : regex, - "bSmart": smart === null ? true : smart, - "bCaseInsensitive": caseInsen === null ? true : caseInsen - } ); - - _fnFilterComplete( settings, settings.oPreviousSearch, 1 ); - } ); - } - ); - - /* - * State API methods - */ - - _api_register( 'state()', function () { - return this.context.length ? - this.context[0].oSavedState : - null; - } ); - - - _api_register( 'state.clear()', function () { - return this.iterator( 'table', function ( settings ) { - // Save an empty object - settings.fnStateSaveCallback.call( settings.oInstance, settings, {} ); - } ); - } ); - - - _api_register( 'state.loaded()', function () { - return this.context.length ? - this.context[0].oLoadedState : - null; - } ); - - - _api_register( 'state.save()', function () { - return this.iterator( 'table', function ( settings ) { - _fnSaveState( settings ); - } ); - } ); - - - - /** - * Provide a common method for plug-ins to check the version of DataTables being - * used, in order to ensure compatibility. - * - * @param {string} version Version string to check for, in the format "X.Y.Z". - * Note that the formats "X" and "X.Y" are also acceptable. - * @returns {boolean} true if this version of DataTables is greater or equal to - * the required version, or false if this version of DataTales is not - * suitable - * @static - * @dtopt API-Static - * - * @example - * alert( $.fn.dataTable.versionCheck( '1.9.0' ) ); - */ - DataTable.versionCheck = DataTable.fnVersionCheck = function( version ) - { - var aThis = DataTable.version.split('.'); - var aThat = version.split('.'); - var iThis, iThat; - - for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) { - iThis = parseInt( aThis[i], 10 ) || 0; - iThat = parseInt( aThat[i], 10 ) || 0; - - // Parts are the same, keep comparing - if (iThis === iThat) { - continue; - } - - // Parts are different, return immediately - return iThis > iThat; - } - - return true; - }; - - - /** - * Check if a `<table>` node is a DataTable table already or not. - * - * @param {node|jquery|string} table Table node, jQuery object or jQuery - * selector for the table to test. Note that if more than more than one - * table is passed on, only the first will be checked - * @returns {boolean} true the table given is a DataTable, or false otherwise - * @static - * @dtopt API-Static - * - * @example - * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { - * $('#example').dataTable(); - * } - */ - DataTable.isDataTable = DataTable.fnIsDataTable = function ( table ) - { - var t = $(table).get(0); - var is = false; - - if ( table instanceof DataTable.Api ) { - return true; - } - - $.each( DataTable.settings, function (i, o) { - var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null; - var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null; - - if ( o.nTable === t || head === t || foot === t ) { - is = true; - } - } ); - - return is; - }; - - - /** - * Get all DataTable tables that have been initialised - optionally you can - * select to get only currently visible tables. - * - * @param {boolean} [visible=false] Flag to indicate if you want all (default) - * or visible tables only. - * @returns {array} Array of `table` nodes (not DataTable instances) which are - * DataTables - * @static - * @dtopt API-Static - * - * @example - * $.each( $.fn.dataTable.tables(true), function () { - * $(table).DataTable().columns.adjust(); - * } ); - */ - DataTable.tables = DataTable.fnTables = function ( visible ) - { - var api = false; - - if ( $.isPlainObject( visible ) ) { - api = visible.api; - visible = visible.visible; - } - - var a = $.map( DataTable.settings, function (o) { - if ( !visible || (visible && $(o.nTable).is(':visible')) ) { - return o.nTable; - } - } ); - - return api ? - new _Api( a ) : - a; - }; - - - /** - * Convert from camel case parameters to Hungarian notation. This is made public - * for the extensions to provide the same ability as DataTables core to accept - * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase - * parameters. - * - * @param {object} src The model object which holds all parameters that can be - * mapped. - * @param {object} user The object to convert from camel case to Hungarian. - * @param {boolean} force When set to `true`, properties which already have a - * Hungarian value in the `user` object will be overwritten. Otherwise they - * won't be. - */ - DataTable.camelToHungarian = _fnCamelToHungarian; - - - - /** - * - */ - _api_register( '$()', function ( selector, opts ) { - var - rows = this.rows( opts ).nodes(), // Get all rows - jqRows = $(rows); - - return $( [].concat( - jqRows.filter( selector ).toArray(), - jqRows.find( selector ).toArray() - ) ); - } ); - - - // jQuery functions to operate on the tables - $.each( [ 'on', 'one', 'off' ], function (i, key) { - _api_register( key+'()', function ( /* event, handler */ ) { - var args = Array.prototype.slice.call(arguments); - - // Add the `dt` namespace automatically if it isn't already present - args[0] = $.map( args[0].split( /\s/ ), function ( e ) { - return ! e.match(/\.dt\b/) ? - e+'.dt' : - e; - } ).join( ' ' ); - - var inst = $( this.tables().nodes() ); - inst[key].apply( inst, args ); - return this; - } ); - } ); - - - _api_register( 'clear()', function () { - return this.iterator( 'table', function ( settings ) { - _fnClearTable( settings ); - } ); - } ); - - - _api_register( 'settings()', function () { - return new _Api( this.context, this.context ); - } ); - - - _api_register( 'init()', function () { - var ctx = this.context; - return ctx.length ? ctx[0].oInit : null; - } ); - - - _api_register( 'data()', function () { - return this.iterator( 'table', function ( settings ) { - return _pluck( settings.aoData, '_aData' ); - } ).flatten(); - } ); - - - _api_register( 'destroy()', function ( remove ) { - remove = remove || false; - - return this.iterator( 'table', function ( settings ) { - var orig = settings.nTableWrapper.parentNode; - var classes = settings.oClasses; - var table = settings.nTable; - var tbody = settings.nTBody; - var thead = settings.nTHead; - var tfoot = settings.nTFoot; - var jqTable = $(table); - var jqTbody = $(tbody); - var jqWrapper = $(settings.nTableWrapper); - var rows = $.map( settings.aoData, function (r) { return r.nTr; } ); - var i, ien; - - // Flag to note that the table is currently being destroyed - no action - // should be taken - settings.bDestroying = true; - - // Fire off the destroy callbacks for plug-ins etc - _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] ); - - // If not being removed from the document, make all columns visible - if ( ! remove ) { - new _Api( settings ).columns().visible( true ); - } - - // Blitz all `DT` namespaced events (these are internal events, the - // lowercase, `dt` events are user subscribed and they are responsible - // for removing them - jqWrapper.off('.DT').find(':not(tbody *)').off('.DT'); - $(window).off('.DT-'+settings.sInstance); - - // When scrolling we had to break the table up - restore it - if ( table != thead.parentNode ) { - jqTable.children('thead').detach(); - jqTable.append( thead ); - } - - if ( tfoot && table != tfoot.parentNode ) { - jqTable.children('tfoot').detach(); - jqTable.append( tfoot ); - } - - settings.aaSorting = []; - settings.aaSortingFixed = []; - _fnSortingClasses( settings ); - - $( rows ).removeClass( settings.asStripeClasses.join(' ') ); - - $('th, td', thead).removeClass( classes.sSortable+' '+ - classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone - ); - - // Add the TR elements back into the table in their original order - jqTbody.children().detach(); - jqTbody.append( rows ); - - // Remove the DataTables generated nodes, events and classes - var removedMethod = remove ? 'remove' : 'detach'; - jqTable[ removedMethod ](); - jqWrapper[ removedMethod ](); - - // If we need to reattach the table to the document - if ( ! remove && orig ) { - // insertBefore acts like appendChild if !arg[1] - orig.insertBefore( table, settings.nTableReinsertBefore ); - - // Restore the width of the original table - was read from the style property, - // so we can restore directly to that - jqTable - .css( 'width', settings.sDestroyWidth ) - .removeClass( classes.sTable ); - - // If the were originally stripe classes - then we add them back here. - // Note this is not fool proof (for example if not all rows had stripe - // classes - but it's a good effort without getting carried away - ien = settings.asDestroyStripes.length; - - if ( ien ) { - jqTbody.children().each( function (i) { - $(this).addClass( settings.asDestroyStripes[i % ien] ); - } ); - } - } - - /* Remove the settings object from the settings array */ - var idx = $.inArray( settings, DataTable.settings ); - if ( idx !== -1 ) { - DataTable.settings.splice( idx, 1 ); - } - } ); - } ); - - - // Add the `every()` method for rows, columns and cells in a compact form - $.each( [ 'column', 'row', 'cell' ], function ( i, type ) { - _api_register( type+'s().every()', function ( fn ) { - var opts = this.selector.opts; - var api = this; - - return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) { - // Rows and columns: - // arg1 - index - // arg2 - table counter - // arg3 - loop counter - // arg4 - undefined - // Cells: - // arg1 - row index - // arg2 - column index - // arg3 - table counter - // arg4 - loop counter - fn.call( - api[ type ]( - arg1, - type==='cell' ? arg2 : opts, - type==='cell' ? opts : undefined - ), - arg1, arg2, arg3, arg4 - ); - } ); - } ); - } ); - - - // i18n method for extensions to be able to use the language object from the - // DataTable - _api_register( 'i18n()', function ( token, def, plural ) { - var ctx = this.context[0]; - var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage ); - - if ( resolved === undefined ) { - resolved = def; - } - - if ( plural !== undefined && $.isPlainObject( resolved ) ) { - resolved = resolved[ plural ] !== undefined ? - resolved[ plural ] : - resolved._; - } - - return resolved.replace( '%d', plural ); // nb: plural might be undefined, - } ); - /** - * Version string for plug-ins to check compatibility. Allowed format is - * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used - * only for non-release builds. See http://semver.org/ for more information. - * @member - * @type string - * @default Version number - */ - DataTable.version = "1.10.23"; - - /** - * Private data store, containing all of the settings objects that are - * created for the tables on a given page. - * - * Note that the `DataTable.settings` object is aliased to - * `jQuery.fn.dataTableExt` through which it may be accessed and - * manipulated, or `jQuery.fn.dataTable.settings`. - * @member - * @type array - * @default [] - * @private - */ - DataTable.settings = []; - - /** - * Object models container, for the various models that DataTables has - * available to it. These models define the objects that are used to hold - * the active state and configuration of the table. - * @namespace - */ - DataTable.models = {}; - - - - /** - * Template object for the way in which DataTables holds information about - * search information for the global filter and individual column filters. - * @namespace - */ - DataTable.models.oSearch = { - /** - * Flag to indicate if the filtering should be case insensitive or not - * @type boolean - * @default true - */ - "bCaseInsensitive": true, - - /** - * Applied search term - * @type string - * @default <i>Empty string</i> - */ - "sSearch": "", - - /** - * Flag to indicate if the search term should be interpreted as a - * regular expression (true) or not (false) and therefore and special - * regex characters escaped. - * @type boolean - * @default false - */ - "bRegex": false, - - /** - * Flag to indicate if DataTables is to use its smart filtering or not. - * @type boolean - * @default true - */ - "bSmart": true - }; - - - - - /** - * Template object for the way in which DataTables holds information about - * each individual row. This is the object format used for the settings - * aoData array. - * @namespace - */ - DataTable.models.oRow = { - /** - * TR element for the row - * @type node - * @default null - */ - "nTr": null, - - /** - * Array of TD elements for each row. This is null until the row has been - * created. - * @type array nodes - * @default [] - */ - "anCells": null, - - /** - * Data object from the original data source for the row. This is either - * an array if using the traditional form of DataTables, or an object if - * using mData options. The exact type will depend on the passed in - * data from the data source, or will be an array if using DOM a data - * source. - * @type array|object - * @default [] - */ - "_aData": [], - - /** - * Sorting data cache - this array is ostensibly the same length as the - * number of columns (although each index is generated only as it is - * needed), and holds the data that is used for sorting each column in the - * row. We do this cache generation at the start of the sort in order that - * the formatting of the sort data need be done only once for each cell - * per sort. This array should not be read from or written to by anything - * other than the master sorting methods. - * @type array - * @default null - * @private - */ - "_aSortData": null, - - /** - * Per cell filtering data cache. As per the sort data cache, used to - * increase the performance of the filtering in DataTables - * @type array - * @default null - * @private - */ - "_aFilterData": null, - - /** - * Filtering data cache. This is the same as the cell filtering cache, but - * in this case a string rather than an array. This is easily computed with - * a join on `_aFilterData`, but is provided as a cache so the join isn't - * needed on every search (memory traded for performance) - * @type array - * @default null - * @private - */ - "_sFilterRow": null, - - /** - * Cache of the class name that DataTables has applied to the row, so we - * can quickly look at this variable rather than needing to do a DOM check - * on className for the nTr property. - * @type string - * @default <i>Empty string</i> - * @private - */ - "_sRowStripe": "", - - /** - * Denote if the original data source was from the DOM, or the data source - * object. This is used for invalidating data, so DataTables can - * automatically read data from the original source, unless uninstructed - * otherwise. - * @type string - * @default null - * @private - */ - "src": null, - - /** - * Index in the aoData array. This saves an indexOf lookup when we have the - * object, but want to know the index - * @type integer - * @default -1 - * @private - */ - "idx": -1 - }; - - - /** - * Template object for the column information object in DataTables. This object - * is held in the settings aoColumns array and contains all the information that - * DataTables needs about each individual column. - * - * Note that this object is related to {@link DataTable.defaults.column} - * but this one is the internal data store for DataTables's cache of columns. - * It should NOT be manipulated outside of DataTables. Any configuration should - * be done through the initialisation options. - * @namespace - */ - DataTable.models.oColumn = { - /** - * Column index. This could be worked out on-the-fly with $.inArray, but it - * is faster to just hold it as a variable - * @type integer - * @default null - */ - "idx": null, - - /** - * A list of the columns that sorting should occur on when this column - * is sorted. That this property is an array allows multi-column sorting - * to be defined for a column (for example first name / last name columns - * would benefit from this). The values are integers pointing to the - * columns to be sorted on (typically it will be a single integer pointing - * at itself, but that doesn't need to be the case). - * @type array - */ - "aDataSort": null, - - /** - * Define the sorting directions that are applied to the column, in sequence - * as the column is repeatedly sorted upon - i.e. the first value is used - * as the sorting direction when the column if first sorted (clicked on). - * Sort it again (click again) and it will move on to the next index. - * Repeat until loop. - * @type array - */ - "asSorting": null, - - /** - * Flag to indicate if the column is searchable, and thus should be included - * in the filtering or not. - * @type boolean - */ - "bSearchable": null, - - /** - * Flag to indicate if the column is sortable or not. - * @type boolean - */ - "bSortable": null, - - /** - * Flag to indicate if the column is currently visible in the table or not - * @type boolean - */ - "bVisible": null, - - /** - * Store for manual type assignment using the `column.type` option. This - * is held in store so we can manipulate the column's `sType` property. - * @type string - * @default null - * @private - */ - "_sManualType": null, - - /** - * Flag to indicate if HTML5 data attributes should be used as the data - * source for filtering or sorting. True is either are. - * @type boolean - * @default false - * @private - */ - "_bAttrSrc": false, - - /** - * Developer definable function that is called whenever a cell is created (Ajax source, - * etc) or processed for input (DOM source). This can be used as a compliment to mRender - * allowing you to modify the DOM element (add background colour for example) when the - * element is available. - * @type function - * @param {element} nTd The TD node that has been created - * @param {*} sData The Data for the cell - * @param {array|object} oData The data for the whole row - * @param {int} iRow The row index for the aoData data store - * @default null - */ - "fnCreatedCell": null, - - /** - * Function to get data from a cell in a column. You should <b>never</b> - * access data directly through _aData internally in DataTables - always use - * the method attached to this property. It allows mData to function as - * required. This function is automatically assigned by the column - * initialisation method - * @type function - * @param {array|object} oData The data array/object for the array - * (i.e. aoData[]._aData) - * @param {string} sSpecific The specific data type you want to get - - * 'display', 'type' 'filter' 'sort' - * @returns {*} The data for the cell from the given row's data - * @default null - */ - "fnGetData": null, - - /** - * Function to set data for a cell in the column. You should <b>never</b> - * set the data directly to _aData internally in DataTables - always use - * this method. It allows mData to function as required. This function - * is automatically assigned by the column initialisation method - * @type function - * @param {array|object} oData The data array/object for the array - * (i.e. aoData[]._aData) - * @param {*} sValue Value to set - * @default null - */ - "fnSetData": null, - - /** - * Property to read the value for the cells in the column from the data - * source array / object. If null, then the default content is used, if a - * function is given then the return from the function is used. - * @type function|int|string|null - * @default null - */ - "mData": null, - - /** - * Partner property to mData which is used (only when defined) to get - * the data - i.e. it is basically the same as mData, but without the - * 'set' option, and also the data fed to it is the result from mData. - * This is the rendering method to match the data method of mData. - * @type function|int|string|null - * @default null - */ - "mRender": null, - - /** - * Unique header TH/TD element for this column - this is what the sorting - * listener is attached to (if sorting is enabled.) - * @type node - * @default null - */ - "nTh": null, - - /** - * Unique footer TH/TD element for this column (if there is one). Not used - * in DataTables as such, but can be used for plug-ins to reference the - * footer for each column. - * @type node - * @default null - */ - "nTf": null, - - /** - * The class to apply to all TD elements in the table's TBODY for the column - * @type string - * @default null - */ - "sClass": null, - - /** - * When DataTables calculates the column widths to assign to each column, - * it finds the longest string in each column and then constructs a - * temporary table and reads the widths from that. The problem with this - * is that "mmm" is much wider then "iiii", but the latter is a longer - * string - thus the calculation can go wrong (doing it properly and putting - * it into an DOM object and measuring that is horribly(!) slow). Thus as - * a "work around" we provide this option. It will append its value to the - * text that is found to be the longest string for the column - i.e. padding. - * @type string - */ - "sContentPadding": null, - - /** - * Allows a default value to be given for a column's data, and will be used - * whenever a null data source is encountered (this can be because mData - * is set to null, or because the data source itself is null). - * @type string - * @default null - */ - "sDefaultContent": null, - - /** - * Name for the column, allowing reference to the column by name as well as - * by index (needs a lookup to work by name). - * @type string - */ - "sName": null, - - /** - * Custom sorting data type - defines which of the available plug-ins in - * afnSortData the custom sorting will use - if any is defined. - * @type string - * @default std - */ - "sSortDataType": 'std', - - /** - * Class to be applied to the header element when sorting on this column - * @type string - * @default null - */ - "sSortingClass": null, - - /** - * Class to be applied to the header element when sorting on this column - - * when jQuery UI theming is used. - * @type string - * @default null - */ - "sSortingClassJUI": null, - - /** - * Title of the column - what is seen in the TH element (nTh). - * @type string - */ - "sTitle": null, - - /** - * Column sorting and filtering type - * @type string - * @default null - */ - "sType": null, - - /** - * Width of the column - * @type string - * @default null - */ - "sWidth": null, - - /** - * Width of the column when it was first "encountered" - * @type string - * @default null - */ - "sWidthOrig": null - }; - - - /* - * Developer note: The properties of the object below are given in Hungarian - * notation, that was used as the interface for DataTables prior to v1.10, however - * from v1.10 onwards the primary interface is camel case. In order to avoid - * breaking backwards compatibility utterly with this change, the Hungarian - * version is still, internally the primary interface, but is is not documented - * - hence the @name tags in each doc comment. This allows a Javascript function - * to create a map from Hungarian notation to camel case (going the other direction - * would require each property to be listed, which would add around 3K to the size - * of DataTables, while this method is about a 0.5K hit). - * - * Ultimately this does pave the way for Hungarian notation to be dropped - * completely, but that is a massive amount of work and will break current - * installs (therefore is on-hold until v2). - */ - - /** - * Initialisation options that can be given to DataTables at initialisation - * time. - * @namespace - */ - DataTable.defaults = { - /** - * An array of data to use for the table, passed in at initialisation which - * will be used in preference to any data which is already in the DOM. This is - * particularly useful for constructing tables purely in Javascript, for - * example with a custom Ajax call. - * @type array - * @default null - * - * @dtopt Option - * @name DataTable.defaults.data - * - * @example - * // Using a 2D array data source - * $(document).ready( function () { - * $('#example').dataTable( { - * "data": [ - * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'], - * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'], - * ], - * "columns": [ - * { "title": "Engine" }, - * { "title": "Browser" }, - * { "title": "Platform" }, - * { "title": "Version" }, - * { "title": "Grade" } - * ] - * } ); - * } ); - * - * @example - * // Using an array of objects as a data source (`data`) - * $(document).ready( function () { - * $('#example').dataTable( { - * "data": [ - * { - * "engine": "Trident", - * "browser": "Internet Explorer 4.0", - * "platform": "Win 95+", - * "version": 4, - * "grade": "X" - * }, - * { - * "engine": "Trident", - * "browser": "Internet Explorer 5.0", - * "platform": "Win 95+", - * "version": 5, - * "grade": "C" - * } - * ], - * "columns": [ - * { "title": "Engine", "data": "engine" }, - * { "title": "Browser", "data": "browser" }, - * { "title": "Platform", "data": "platform" }, - * { "title": "Version", "data": "version" }, - * { "title": "Grade", "data": "grade" } - * ] - * } ); - * } ); - */ - "aaData": null, - - - /** - * If ordering is enabled, then DataTables will perform a first pass sort on - * initialisation. You can define which column(s) the sort is performed - * upon, and the sorting direction, with this variable. The `sorting` array - * should contain an array for each column to be sorted initially containing - * the column's index and a direction string ('asc' or 'desc'). - * @type array - * @default [[0,'asc']] - * - * @dtopt Option - * @name DataTable.defaults.order - * - * @example - * // Sort by 3rd column first, and then 4th column - * $(document).ready( function() { - * $('#example').dataTable( { - * "order": [[2,'asc'], [3,'desc']] - * } ); - * } ); - * - * // No initial sorting - * $(document).ready( function() { - * $('#example').dataTable( { - * "order": [] - * } ); - * } ); - */ - "aaSorting": [[0,'asc']], - - - /** - * This parameter is basically identical to the `sorting` parameter, but - * cannot be overridden by user interaction with the table. What this means - * is that you could have a column (visible or hidden) which the sorting - * will always be forced on first - any sorting after that (from the user) - * will then be performed as required. This can be useful for grouping rows - * together. - * @type array - * @default null - * - * @dtopt Option - * @name DataTable.defaults.orderFixed - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "orderFixed": [[0,'asc']] - * } ); - * } ) - */ - "aaSortingFixed": [], - - - /** - * DataTables can be instructed to load data to display in the table from a - * Ajax source. This option defines how that Ajax call is made and where to. - * - * The `ajax` property has three different modes of operation, depending on - * how it is defined. These are: - * - * * `string` - Set the URL from where the data should be loaded from. - * * `object` - Define properties for `jQuery.ajax`. - * * `function` - Custom data get function - * - * `string` - * -------- - * - * As a string, the `ajax` property simply defines the URL from which - * DataTables will load data. - * - * `object` - * -------- - * - * As an object, the parameters in the object are passed to - * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control - * of the Ajax request. DataTables has a number of default parameters which - * you can override using this option. Please refer to the jQuery - * documentation for a full description of the options available, although - * the following parameters provide additional options in DataTables or - * require special consideration: - * - * * `data` - As with jQuery, `data` can be provided as an object, but it - * can also be used as a function to manipulate the data DataTables sends - * to the server. The function takes a single parameter, an object of - * parameters with the values that DataTables has readied for sending. An - * object may be returned which will be merged into the DataTables - * defaults, or you can add the items to the object that was passed in and - * not return anything from the function. This supersedes `fnServerParams` - * from DataTables 1.9-. - * - * * `dataSrc` - By default DataTables will look for the property `data` (or - * `aaData` for compatibility with DataTables 1.9-) when obtaining data - * from an Ajax source or for server-side processing - this parameter - * allows that property to be changed. You can use Javascript dotted - * object notation to get a data source for multiple levels of nesting, or - * it my be used as a function. As a function it takes a single parameter, - * the JSON returned from the server, which can be manipulated as - * required, with the returned value being that used by DataTables as the - * data source for the table. This supersedes `sAjaxDataProp` from - * DataTables 1.9-. - * - * * `success` - Should not be overridden it is used internally in - * DataTables. To manipulate / transform the data returned by the server - * use `ajax.dataSrc`, or use `ajax` as a function (see below). - * - * `function` - * ---------- - * - * As a function, making the Ajax call is left up to yourself allowing - * complete control of the Ajax request. Indeed, if desired, a method other - * than Ajax could be used to obtain the required data, such as Web storage - * or an AIR database. - * - * The function is given four parameters and no return is required. The - * parameters are: - * - * 1. _object_ - Data to send to the server - * 2. _function_ - Callback function that must be executed when the required - * data has been obtained. That data should be passed into the callback - * as the only parameter - * 3. _object_ - DataTables settings object for the table - * - * Note that this supersedes `fnServerData` from DataTables 1.9-. - * - * @type string|object|function - * @default null - * - * @dtopt Option - * @name DataTable.defaults.ajax - * @since 1.10.0 - * - * @example - * // Get JSON data from a file via Ajax. - * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default). - * $('#example').dataTable( { - * "ajax": "data.json" - * } ); - * - * @example - * // Get JSON data from a file via Ajax, using `dataSrc` to change - * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`) - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": "tableData" - * } - * } ); - * - * @example - * // Get JSON data from a file via Ajax, using `dataSrc` to read data - * // from a plain array rather than an array in an object - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": "" - * } - * } ); - * - * @example - * // Manipulate the data returned from the server - add a link to data - * // (note this can, should, be done using `render` for the column - this - * // is just a simple example of how the data can be manipulated). - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "dataSrc": function ( json ) { - * for ( var i=0, ien=json.length ; i<ien ; i++ ) { - * json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>'; - * } - * return json; - * } - * } - * } ); - * - * @example - * // Add data to the request - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "data": function ( d ) { - * return { - * "extra_search": $('#extra').val() - * }; - * } - * } - * } ); - * - * @example - * // Send request as POST - * $('#example').dataTable( { - * "ajax": { - * "url": "data.json", - * "type": "POST" - * } - * } ); - * - * @example - * // Get the data from localStorage (could interface with a form for - * // adding, editing and removing rows). - * $('#example').dataTable( { - * "ajax": function (data, callback, settings) { - * callback( - * JSON.parse( localStorage.getItem('dataTablesData') ) - * ); - * } - * } ); - */ - "ajax": null, - - - /** - * This parameter allows you to readily specify the entries in the length drop - * down menu that DataTables shows when pagination is enabled. It can be - * either a 1D array of options which will be used for both the displayed - * option and the value, or a 2D array which will use the array in the first - * position as the value, and the array in the second position as the - * displayed options (useful for language strings such as 'All'). - * - * Note that the `pageLength` property will be automatically set to the - * first value given in this array, unless `pageLength` is also provided. - * @type array - * @default [ 10, 25, 50, 100 ] - * - * @dtopt Option - * @name DataTable.defaults.lengthMenu - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]] - * } ); - * } ); - */ - "aLengthMenu": [ 10, 25, 50, 100 ], - - - /** - * The `columns` option in the initialisation parameter allows you to define - * details about the way individual columns behave. For a full list of - * column options that can be set, please see - * {@link DataTable.defaults.column}. Note that if you use `columns` to - * define your columns, you must have an entry in the array for every single - * column that you have in your table (these can be null if you don't which - * to specify any options). - * @member - * - * @name DataTable.defaults.column - */ - "aoColumns": null, - - /** - * Very similar to `columns`, `columnDefs` allows you to target a specific - * column, multiple columns, or all columns, using the `targets` property of - * each object in the array. This allows great flexibility when creating - * tables, as the `columnDefs` arrays can be of any length, targeting the - * columns you specifically want. `columnDefs` may use any of the column - * options available: {@link DataTable.defaults.column}, but it _must_ - * have `targets` defined in each object in the array. Values in the `targets` - * array may be: - * <ul> - * <li>a string - class name will be matched on the TH for the column</li> - * <li>0 or a positive integer - column index counting from the left</li> - * <li>a negative integer - column index counting from the right</li> - * <li>the string "_all" - all columns (i.e. assign a default)</li> - * </ul> - * @member - * - * @name DataTable.defaults.columnDefs - */ - "aoColumnDefs": null, - - - /** - * Basically the same as `search`, this parameter defines the individual column - * filtering state at initialisation time. The array must be of the same size - * as the number of columns, and each element be an object with the parameters - * `search` and `escapeRegex` (the latter is optional). 'null' is also - * accepted and the default will be used. - * @type array - * @default [] - * - * @dtopt Option - * @name DataTable.defaults.searchCols - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "searchCols": [ - * null, - * { "search": "My filter" }, - * null, - * { "search": "^[0-9]", "escapeRegex": false } - * ] - * } ); - * } ) - */ - "aoSearchCols": [], - - - /** - * An array of CSS classes that should be applied to displayed rows. This - * array may be of any length, and DataTables will apply each class - * sequentially, looping when required. - * @type array - * @default null <i>Will take the values determined by the `oClasses.stripe*` - * options</i> - * - * @dtopt Option - * @name DataTable.defaults.stripeClasses - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ] - * } ); - * } ) - */ - "asStripeClasses": null, - - - /** - * Enable or disable automatic column width calculation. This can be disabled - * as an optimisation (it takes some time to calculate the widths) if the - * tables widths are passed in using `columns`. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.autoWidth - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "autoWidth": false - * } ); - * } ); - */ - "bAutoWidth": true, - - - /** - * Deferred rendering can provide DataTables with a huge speed boost when you - * are using an Ajax or JS data source for the table. This option, when set to - * true, will cause DataTables to defer the creation of the table elements for - * each row until they are needed for a draw - saving a significant amount of - * time. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.deferRender - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajax": "sources/arrays.txt", - * "deferRender": true - * } ); - * } ); - */ - "bDeferRender": false, - - - /** - * Replace a DataTable which matches the given selector and replace it with - * one which has the properties of the new initialisation object passed. If no - * table matches the selector, then the new DataTable will be constructed as - * per normal. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.destroy - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "srollY": "200px", - * "paginate": false - * } ); - * - * // Some time later.... - * $('#example').dataTable( { - * "filter": false, - * "destroy": true - * } ); - * } ); - */ - "bDestroy": false, - - - /** - * Enable or disable filtering of data. Filtering in DataTables is "smart" in - * that it allows the end user to input multiple words (space separated) and - * will match a row containing those words, even if not in the order that was - * specified (this allow matching across multiple columns). Note that if you - * wish to use filtering in DataTables this must remain 'true' - to remove the - * default filtering input box and retain filtering abilities, please use - * {@link DataTable.defaults.dom}. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.searching - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "searching": false - * } ); - * } ); - */ - "bFilter": true, - - - /** - * Enable or disable the table information display. This shows information - * about the data that is currently visible on the page, including information - * about filtered data if that action is being performed. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.info - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "info": false - * } ); - * } ); - */ - "bInfo": true, - - - /** - * Allows the end user to select the size of a formatted page from a select - * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`). - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.lengthChange - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "lengthChange": false - * } ); - * } ); - */ - "bLengthChange": true, - - - /** - * Enable or disable pagination. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.paging - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "paging": false - * } ); - * } ); - */ - "bPaginate": true, - - - /** - * Enable or disable the display of a 'processing' indicator when the table is - * being processed (e.g. a sort). This is particularly useful for tables with - * large amounts of data where it can take a noticeable amount of time to sort - * the entries. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.processing - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "processing": true - * } ); - * } ); - */ - "bProcessing": false, - - - /** - * Retrieve the DataTables object for the given selector. Note that if the - * table has already been initialised, this parameter will cause DataTables - * to simply return the object that has already been set up - it will not take - * account of any changes you might have made to the initialisation object - * passed to DataTables (setting this parameter to true is an acknowledgement - * that you understand this). `destroy` can be used to reinitialise a table if - * you need. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.retrieve - * - * @example - * $(document).ready( function() { - * initTable(); - * tableActions(); - * } ); - * - * function initTable () - * { - * return $('#example').dataTable( { - * "scrollY": "200px", - * "paginate": false, - * "retrieve": true - * } ); - * } - * - * function tableActions () - * { - * var table = initTable(); - * // perform API operations with oTable - * } - */ - "bRetrieve": false, - - - /** - * When vertical (y) scrolling is enabled, DataTables will force the height of - * the table's viewport to the given height at all times (useful for layout). - * However, this can look odd when filtering data down to a small data set, - * and the footer is left "floating" further down. This parameter (when - * enabled) will cause DataTables to collapse the table's viewport down when - * the result set will fit within the given Y height. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.scrollCollapse - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollY": "200", - * "scrollCollapse": true - * } ); - * } ); - */ - "bScrollCollapse": false, - - - /** - * Configure DataTables to use server-side processing. Note that the - * `ajax` parameter must also be given in order to give DataTables a - * source to obtain the required data for each draw. - * @type boolean - * @default false - * - * @dtopt Features - * @dtopt Server-side - * @name DataTable.defaults.serverSide - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "xhr.php" - * } ); - * } ); - */ - "bServerSide": false, - - - /** - * Enable or disable sorting of columns. Sorting of individual columns can be - * disabled by the `sortable` option for each column. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.ordering - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "ordering": false - * } ); - * } ); - */ - "bSort": true, - - - /** - * Enable or display DataTables' ability to sort multiple columns at the - * same time (activated by shift-click by the user). - * @type boolean - * @default true - * - * @dtopt Options - * @name DataTable.defaults.orderMulti - * - * @example - * // Disable multiple column sorting ability - * $(document).ready( function () { - * $('#example').dataTable( { - * "orderMulti": false - * } ); - * } ); - */ - "bSortMulti": true, - - - /** - * Allows control over whether DataTables should use the top (true) unique - * cell that is found for a single column, or the bottom (false - default). - * This is useful when using complex headers. - * @type boolean - * @default false - * - * @dtopt Options - * @name DataTable.defaults.orderCellsTop - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "orderCellsTop": true - * } ); - * } ); - */ - "bSortCellsTop": false, - - - /** - * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and - * `sorting\_3` to the columns which are currently being sorted on. This is - * presented as a feature switch as it can increase processing time (while - * classes are removed and added) so for large data sets you might want to - * turn this off. - * @type boolean - * @default true - * - * @dtopt Features - * @name DataTable.defaults.orderClasses - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "orderClasses": false - * } ); - * } ); - */ - "bSortClasses": true, - - - /** - * Enable or disable state saving. When enabled HTML5 `localStorage` will be - * used to save table display information such as pagination information, - * display length, filtering and sorting. As such when the end user reloads - * the page the display display will match what thy had previously set up. - * - * Due to the use of `localStorage` the default state saving is not supported - * in IE6 or 7. If state saving is required in those browsers, use - * `stateSaveCallback` to provide a storage solution such as cookies. - * @type boolean - * @default false - * - * @dtopt Features - * @name DataTable.defaults.stateSave - * - * @example - * $(document).ready( function () { - * $('#example').dataTable( { - * "stateSave": true - * } ); - * } ); - */ - "bStateSave": false, - - - /** - * This function is called when a TR element is created (and all TD child - * elements have been inserted), or registered if using a DOM source, allowing - * manipulation of the TR element (adding classes etc). - * @type function - * @param {node} row "TR" element for the current row - * @param {array} data Raw data array for this row - * @param {int} dataIndex The index of this row in the internal aoData array - * - * @dtopt Callbacks - * @name DataTable.defaults.createdRow - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "createdRow": function( row, data, dataIndex ) { - * // Bold the grade for all 'A' grade browsers - * if ( data[4] == "A" ) - * { - * $('td:eq(4)', row).html( '<b>A</b>' ); - * } - * } - * } ); - * } ); - */ - "fnCreatedRow": null, - - - /** - * This function is called on every 'draw' event, and allows you to - * dynamically modify any aspect you want about the created DOM. - * @type function - * @param {object} settings DataTables settings object - * - * @dtopt Callbacks - * @name DataTable.defaults.drawCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "drawCallback": function( settings ) { - * alert( 'DataTables has redrawn the table' ); - * } - * } ); - * } ); - */ - "fnDrawCallback": null, - - - /** - * Identical to fnHeaderCallback() but for the table footer this function - * allows you to modify the table footer on every 'draw' event. - * @type function - * @param {node} foot "TR" element for the footer - * @param {array} data Full table data (as derived from the original HTML) - * @param {int} start Index for the current display starting point in the - * display array - * @param {int} end Index for the current display ending point in the - * display array - * @param {array int} display Index array to translate the visual position - * to the full data array - * - * @dtopt Callbacks - * @name DataTable.defaults.footerCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "footerCallback": function( tfoot, data, start, end, display ) { - * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start; - * } - * } ); - * } ) - */ - "fnFooterCallback": null, - - - /** - * When rendering large numbers in the information element for the table - * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers - * to have a comma separator for the 'thousands' units (e.g. 1 million is - * rendered as "1,000,000") to help readability for the end user. This - * function will override the default method DataTables uses. - * @type function - * @member - * @param {int} toFormat number to be formatted - * @returns {string} formatted string for DataTables to show the number - * - * @dtopt Callbacks - * @name DataTable.defaults.formatNumber - * - * @example - * // Format a number using a single quote for the separator (note that - * // this can also be done with the language.thousands option) - * $(document).ready( function() { - * $('#example').dataTable( { - * "formatNumber": function ( toFormat ) { - * return toFormat.toString().replace( - * /\B(?=(\d{3})+(?!\d))/g, "'" - * ); - * }; - * } ); - * } ); - */ - "fnFormatNumber": function ( toFormat ) { - return toFormat.toString().replace( - /\B(?=(\d{3})+(?!\d))/g, - this.oLanguage.sThousands - ); - }, - - - /** - * This function is called on every 'draw' event, and allows you to - * dynamically modify the header row. This can be used to calculate and - * display useful information about the table. - * @type function - * @param {node} head "TR" element for the header - * @param {array} data Full table data (as derived from the original HTML) - * @param {int} start Index for the current display starting point in the - * display array - * @param {int} end Index for the current display ending point in the - * display array - * @param {array int} display Index array to translate the visual position - * to the full data array - * - * @dtopt Callbacks - * @name DataTable.defaults.headerCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "fheaderCallback": function( head, data, start, end, display ) { - * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records"; - * } - * } ); - * } ) - */ - "fnHeaderCallback": null, - - - /** - * The information element can be used to convey information about the current - * state of the table. Although the internationalisation options presented by - * DataTables are quite capable of dealing with most customisations, there may - * be times where you wish to customise the string further. This callback - * allows you to do exactly that. - * @type function - * @param {object} oSettings DataTables settings object - * @param {int} start Starting position in data for the draw - * @param {int} end End position in data for the draw - * @param {int} max Total number of rows in the table (regardless of - * filtering) - * @param {int} total Total number of rows in the data set, after filtering - * @param {string} pre The string that DataTables has formatted using it's - * own rules - * @returns {string} The string to be displayed in the information element. - * - * @dtopt Callbacks - * @name DataTable.defaults.infoCallback - * - * @example - * $('#example').dataTable( { - * "infoCallback": function( settings, start, end, max, total, pre ) { - * return start +" to "+ end; - * } - * } ); - */ - "fnInfoCallback": null, - - - /** - * Called when the table has been initialised. Normally DataTables will - * initialise sequentially and there will be no need for this function, - * however, this does not hold true when using external language information - * since that is obtained using an async XHR call. - * @type function - * @param {object} settings DataTables settings object - * @param {object} json The JSON object request from the server - only - * present if client-side Ajax sourced data is used - * - * @dtopt Callbacks - * @name DataTable.defaults.initComplete - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "initComplete": function(settings, json) { - * alert( 'DataTables has finished its initialisation.' ); - * } - * } ); - * } ) - */ - "fnInitComplete": null, - - - /** - * Called at the very start of each table draw and can be used to cancel the - * draw by returning false, any other return (including undefined) results in - * the full draw occurring). - * @type function - * @param {object} settings DataTables settings object - * @returns {boolean} False will cancel the draw, anything else (including no - * return) will allow it to complete. - * - * @dtopt Callbacks - * @name DataTable.defaults.preDrawCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "preDrawCallback": function( settings ) { - * if ( $('#test').val() == 1 ) { - * return false; - * } - * } - * } ); - * } ); - */ - "fnPreDrawCallback": null, - - - /** - * This function allows you to 'post process' each row after it have been - * generated for each table draw, but before it is rendered on screen. This - * function might be used for setting the row class name etc. - * @type function - * @param {node} row "TR" element for the current row - * @param {array} data Raw data array for this row - * @param {int} displayIndex The display index for the current table draw - * @param {int} displayIndexFull The index of the data in the full list of - * rows (after filtering) - * - * @dtopt Callbacks - * @name DataTable.defaults.rowCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "rowCallback": function( row, data, displayIndex, displayIndexFull ) { - * // Bold the grade for all 'A' grade browsers - * if ( data[4] == "A" ) { - * $('td:eq(4)', row).html( '<b>A</b>' ); - * } - * } - * } ); - * } ); - */ - "fnRowCallback": null, - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * This parameter allows you to override the default function which obtains - * the data from the server so something more suitable for your application. - * For example you could use POST data, or pull information from a Gears or - * AIR database. - * @type function - * @member - * @param {string} source HTTP source to obtain the data from (`ajax`) - * @param {array} data A key/value pair object containing the data to send - * to the server - * @param {function} callback to be called on completion of the data get - * process that will draw the data on the page. - * @param {object} settings DataTables settings object - * - * @dtopt Callbacks - * @dtopt Server-side - * @name DataTable.defaults.serverData - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "fnServerData": null, - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * It is often useful to send extra data to the server when making an Ajax - * request - for example custom filtering information, and this callback - * function makes it trivial to send extra information to the server. The - * passed in parameter is the data set that has been constructed by - * DataTables, and you can add to this or modify it as you require. - * @type function - * @param {array} data Data array (array of objects which are name/value - * pairs) that has been constructed by DataTables and will be sent to the - * server. In the case of Ajax sourced data with server-side processing - * this will be an empty array, for server-side processing there will be a - * significant number of parameters! - * @returns {undefined} Ensure that you modify the data array passed in, - * as this is passed by reference. - * - * @dtopt Callbacks - * @dtopt Server-side - * @name DataTable.defaults.serverParams - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "fnServerParams": null, - - - /** - * Load the table state. With this function you can define from where, and how, the - * state of a table is loaded. By default DataTables will load from `localStorage` - * but you might wish to use a server-side database or cookies. - * @type function - * @member - * @param {object} settings DataTables settings object - * @param {object} callback Callback that can be executed when done. It - * should be passed the loaded state object. - * @return {object} The DataTables state object to be loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoadCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadCallback": function (settings, callback) { - * $.ajax( { - * "url": "/state_load", - * "dataType": "json", - * "success": function (json) { - * callback( json ); - * } - * } ); - * } - * } ); - * } ); - */ - "fnStateLoadCallback": function ( settings ) { - try { - return JSON.parse( - (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( - 'DataTables_'+settings.sInstance+'_'+location.pathname - ) - ); - } catch (e) { - return {}; - } - }, - - - /** - * Callback which allows modification of the saved state prior to loading that state. - * This callback is called when the table is loading state from the stored data, but - * prior to the settings object being modified by the saved state. Note that for - * plug-in authors, you should use the `stateLoadParams` event to load parameters for - * a plug-in. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object that is to be loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoadParams - * - * @example - * // Remove a saved filter, so filtering is never loaded - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadParams": function (settings, data) { - * data.oSearch.sSearch = ""; - * } - * } ); - * } ); - * - * @example - * // Disallow state loading by returning false - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoadParams": function (settings, data) { - * return false; - * } - * } ); - * } ); - */ - "fnStateLoadParams": null, - - - /** - * Callback that is called when the state has been loaded from the state saving method - * and the DataTables settings object has been modified as a result of the loaded state. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object that was loaded - * - * @dtopt Callbacks - * @name DataTable.defaults.stateLoaded - * - * @example - * // Show an alert with the filtering value that was saved - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateLoaded": function (settings, data) { - * alert( 'Saved filter was: '+data.oSearch.sSearch ); - * } - * } ); - * } ); - */ - "fnStateLoaded": null, - - - /** - * Save the table state. This function allows you to define where and how the state - * information for the table is stored By default DataTables will use `localStorage` - * but you might wish to use a server-side database or cookies. - * @type function - * @member - * @param {object} settings DataTables settings object - * @param {object} data The state object to be saved - * - * @dtopt Callbacks - * @name DataTable.defaults.stateSaveCallback - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateSaveCallback": function (settings, data) { - * // Send an Ajax request to the server with the state object - * $.ajax( { - * "url": "/state_save", - * "data": data, - * "dataType": "json", - * "method": "POST" - * "success": function () {} - * } ); - * } - * } ); - * } ); - */ - "fnStateSaveCallback": function ( settings, data ) { - try { - (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( - 'DataTables_'+settings.sInstance+'_'+location.pathname, - JSON.stringify( data ) - ); - } catch (e) {} - }, - - - /** - * Callback which allows modification of the state to be saved. Called when the table - * has changed state a new state save is required. This method allows modification of - * the state saving object prior to actually doing the save, including addition or - * other state properties or modification. Note that for plug-in authors, you should - * use the `stateSaveParams` event to save parameters for a plug-in. - * @type function - * @param {object} settings DataTables settings object - * @param {object} data The state object to be saved - * - * @dtopt Callbacks - * @name DataTable.defaults.stateSaveParams - * - * @example - * // Remove a saved filter, so filtering is never saved - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateSave": true, - * "stateSaveParams": function (settings, data) { - * data.oSearch.sSearch = ""; - * } - * } ); - * } ); - */ - "fnStateSaveParams": null, - - - /** - * Duration for which the saved state information is considered valid. After this period - * has elapsed the state will be returned to the default. - * Value is given in seconds. - * @type int - * @default 7200 <i>(2 hours)</i> - * - * @dtopt Options - * @name DataTable.defaults.stateDuration - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "stateDuration": 60*60*24; // 1 day - * } ); - * } ) - */ - "iStateDuration": 7200, - - - /** - * When enabled DataTables will not make a request to the server for the first - * page draw - rather it will use the data already on the page (no sorting etc - * will be applied to it), thus saving on an XHR at load time. `deferLoading` - * is used to indicate that deferred loading is required, but it is also used - * to tell DataTables how many records there are in the full table (allowing - * the information element and pagination to be displayed correctly). In the case - * where a filtering is applied to the table on initial load, this can be - * indicated by giving the parameter as an array, where the first element is - * the number of records available after filtering and the second element is the - * number of records without filtering (allowing the table information element - * to be shown correctly). - * @type int | array - * @default null - * - * @dtopt Options - * @name DataTable.defaults.deferLoading - * - * @example - * // 57 records available in the table, no filtering applied - * $(document).ready( function() { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "scripts/server_processing.php", - * "deferLoading": 57 - * } ); - * } ); - * - * @example - * // 57 records after filtering, 100 without filtering (an initial filter applied) - * $(document).ready( function() { - * $('#example').dataTable( { - * "serverSide": true, - * "ajax": "scripts/server_processing.php", - * "deferLoading": [ 57, 100 ], - * "search": { - * "search": "my_filter" - * } - * } ); - * } ); - */ - "iDeferLoading": null, - - - /** - * Number of rows to display on a single page when using pagination. If - * feature enabled (`lengthChange`) then the end user will be able to override - * this to a custom setting using a pop-up menu. - * @type int - * @default 10 - * - * @dtopt Options - * @name DataTable.defaults.pageLength - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "pageLength": 50 - * } ); - * } ) - */ - "iDisplayLength": 10, - - - /** - * Define the starting point for data display when using DataTables with - * pagination. Note that this parameter is the number of records, rather than - * the page number, so if you have 10 records per page and want to start on - * the third page, it should be "20". - * @type int - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.displayStart - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "displayStart": 20 - * } ); - * } ) - */ - "iDisplayStart": 0, - - - /** - * By default DataTables allows keyboard navigation of the table (sorting, paging, - * and filtering) by adding a `tabindex` attribute to the required elements. This - * allows you to tab through the controls and press the enter key to activate them. - * The tabindex is default 0, meaning that the tab follows the flow of the document. - * You can overrule this using this parameter if you wish. Use a value of -1 to - * disable built-in keyboard navigation. - * @type int - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.tabIndex - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "tabIndex": 1 - * } ); - * } ); - */ - "iTabIndex": 0, - - - /** - * Classes that DataTables assigns to the various components and features - * that it adds to the HTML table. This allows classes to be configured - * during initialisation in addition to through the static - * {@link DataTable.ext.oStdClasses} object). - * @namespace - * @name DataTable.defaults.classes - */ - "oClasses": {}, - - - /** - * All strings that DataTables uses in the user interface that it creates - * are defined in this object, allowing you to modified them individually or - * completely replace them all as required. - * @namespace - * @name DataTable.defaults.language - */ - "oLanguage": { - /** - * Strings that are used for WAI-ARIA labels and controls only (these are not - * actually visible on the page, but will be read by screenreaders, and thus - * must be internationalised as well). - * @namespace - * @name DataTable.defaults.language.aria - */ - "oAria": { - /** - * ARIA label that is added to the table headers when the column may be - * sorted ascending by activing the column (click or return when focused). - * Note that the column header is prefixed to this string. - * @type string - * @default : activate to sort column ascending - * - * @dtopt Language - * @name DataTable.defaults.language.aria.sortAscending - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "aria": { - * "sortAscending": " - click/return to sort ascending" - * } - * } - * } ); - * } ); - */ - "sSortAscending": ": activate to sort column ascending", - - /** - * ARIA label that is added to the table headers when the column may be - * sorted descending by activing the column (click or return when focused). - * Note that the column header is prefixed to this string. - * @type string - * @default : activate to sort column ascending - * - * @dtopt Language - * @name DataTable.defaults.language.aria.sortDescending - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "aria": { - * "sortDescending": " - click/return to sort descending" - * } - * } - * } ); - * } ); - */ - "sSortDescending": ": activate to sort column descending" - }, - - /** - * Pagination string used by DataTables for the built-in pagination - * control types. - * @namespace - * @name DataTable.defaults.language.paginate - */ - "oPaginate": { - /** - * Text to use when using the 'full_numbers' type of pagination for the - * button to take the user to the first page. - * @type string - * @default First - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.first - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "first": "First page" - * } - * } - * } ); - * } ); - */ - "sFirst": "First", - - - /** - * Text to use when using the 'full_numbers' type of pagination for the - * button to take the user to the last page. - * @type string - * @default Last - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.last - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "last": "Last page" - * } - * } - * } ); - * } ); - */ - "sLast": "Last", - - - /** - * Text to use for the 'next' pagination button (to take the user to the - * next page). - * @type string - * @default Next - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.next - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "next": "Next page" - * } - * } - * } ); - * } ); - */ - "sNext": "Next", - - - /** - * Text to use for the 'previous' pagination button (to take the user to - * the previous page). - * @type string - * @default Previous - * - * @dtopt Language - * @name DataTable.defaults.language.paginate.previous - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "paginate": { - * "previous": "Previous page" - * } - * } - * } ); - * } ); - */ - "sPrevious": "Previous" - }, - - /** - * This string is shown in preference to `zeroRecords` when the table is - * empty of data (regardless of filtering). Note that this is an optional - * parameter - if it is not given, the value of `zeroRecords` will be used - * instead (either the default or given value). - * @type string - * @default No data available in table - * - * @dtopt Language - * @name DataTable.defaults.language.emptyTable - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "emptyTable": "No data available in table" - * } - * } ); - * } ); - */ - "sEmptyTable": "No data available in table", - - - /** - * This string gives information to the end user about the information - * that is current on display on the page. The following tokens can be - * used in the string and will be dynamically replaced as the table - * display updates. This tokens can be placed anywhere in the string, or - * removed as needed by the language requires: - * - * * `\_START\_` - Display index of the first record on the current page - * * `\_END\_` - Display index of the last record on the current page - * * `\_TOTAL\_` - Number of records in the table after filtering - * * `\_MAX\_` - Number of records in the table without filtering - * * `\_PAGE\_` - Current page number - * * `\_PAGES\_` - Total number of pages of data in the table - * - * @type string - * @default Showing _START_ to _END_ of _TOTAL_ entries - * - * @dtopt Language - * @name DataTable.defaults.language.info - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "info": "Showing page _PAGE_ of _PAGES_" - * } - * } ); - * } ); - */ - "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", - - - /** - * Display information string for when the table is empty. Typically the - * format of this string should match `info`. - * @type string - * @default Showing 0 to 0 of 0 entries - * - * @dtopt Language - * @name DataTable.defaults.language.infoEmpty - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoEmpty": "No entries to show" - * } - * } ); - * } ); - */ - "sInfoEmpty": "Showing 0 to 0 of 0 entries", - - - /** - * When a user filters the information in a table, this string is appended - * to the information (`info`) to give an idea of how strong the filtering - * is. The variable _MAX_ is dynamically updated. - * @type string - * @default (filtered from _MAX_ total entries) - * - * @dtopt Language - * @name DataTable.defaults.language.infoFiltered - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoFiltered": " - filtering from _MAX_ records" - * } - * } ); - * } ); - */ - "sInfoFiltered": "(filtered from _MAX_ total entries)", - - - /** - * If can be useful to append extra information to the info string at times, - * and this variable does exactly that. This information will be appended to - * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are - * being used) at all times. - * @type string - * @default <i>Empty string</i> - * - * @dtopt Language - * @name DataTable.defaults.language.infoPostFix - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "infoPostFix": "All records shown are derived from real information." - * } - * } ); - * } ); - */ - "sInfoPostFix": "", - - - /** - * This decimal place operator is a little different from the other - * language options since DataTables doesn't output floating point - * numbers, so it won't ever use this for display of a number. Rather, - * what this parameter does is modify the sort methods of the table so - * that numbers which are in a format which has a character other than - * a period (`.`) as a decimal place will be sorted numerically. - * - * Note that numbers with different decimal places cannot be shown in - * the same table and still be sortable, the table must be consistent. - * However, multiple different tables on the page can use different - * decimal place characters. - * @type string - * @default - * - * @dtopt Language - * @name DataTable.defaults.language.decimal - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "decimal": "," - * "thousands": "." - * } - * } ); - * } ); - */ - "sDecimal": "", - - - /** - * DataTables has a build in number formatter (`formatNumber`) which is - * used to format large numbers that are used in the table information. - * By default a comma is used, but this can be trivially changed to any - * character you wish with this parameter. - * @type string - * @default , - * - * @dtopt Language - * @name DataTable.defaults.language.thousands - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "thousands": "'" - * } - * } ); - * } ); - */ - "sThousands": ",", - - - /** - * Detail the action that will be taken when the drop down menu for the - * pagination length option is changed. The '_MENU_' variable is replaced - * with a default select list of 10, 25, 50 and 100, and can be replaced - * with a custom select box if required. - * @type string - * @default Show _MENU_ entries - * - * @dtopt Language - * @name DataTable.defaults.language.lengthMenu - * - * @example - * // Language change only - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "lengthMenu": "Display _MENU_ records" - * } - * } ); - * } ); - * - * @example - * // Language and options change - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "lengthMenu": 'Display <select>'+ - * '<option value="10">10</option>'+ - * '<option value="20">20</option>'+ - * '<option value="30">30</option>'+ - * '<option value="40">40</option>'+ - * '<option value="50">50</option>'+ - * '<option value="-1">All</option>'+ - * '</select> records' - * } - * } ); - * } ); - */ - "sLengthMenu": "Show _MENU_ entries", - - - /** - * When using Ajax sourced data and during the first draw when DataTables is - * gathering the data, this message is shown in an empty row in the table to - * indicate to the end user the the data is being loaded. Note that this - * parameter is not used when loading data by server-side processing, just - * Ajax sourced data with client-side processing. - * @type string - * @default Loading... - * - * @dtopt Language - * @name DataTable.defaults.language.loadingRecords - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "loadingRecords": "Please wait - loading..." - * } - * } ); - * } ); - */ - "sLoadingRecords": "Loading...", - - - /** - * Text which is displayed when the table is processing a user action - * (usually a sort command or similar). - * @type string - * @default Processing... - * - * @dtopt Language - * @name DataTable.defaults.language.processing - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "processing": "DataTables is currently busy" - * } - * } ); - * } ); - */ - "sProcessing": "Processing...", - - - /** - * Details the actions that will be taken when the user types into the - * filtering input text box. The variable "_INPUT_", if used in the string, - * is replaced with the HTML text box for the filtering input allowing - * control over where it appears in the string. If "_INPUT_" is not given - * then the input box is appended to the string automatically. - * @type string - * @default Search: - * - * @dtopt Language - * @name DataTable.defaults.language.search - * - * @example - * // Input text box will be appended at the end automatically - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "search": "Filter records:" - * } - * } ); - * } ); - * - * @example - * // Specify where the filter should appear - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "search": "Apply filter _INPUT_ to table" - * } - * } ); - * } ); - */ - "sSearch": "Search:", - - - /** - * Assign a `placeholder` attribute to the search `input` element - * @type string - * @default - * - * @dtopt Language - * @name DataTable.defaults.language.searchPlaceholder - */ - "sSearchPlaceholder": "", - - - /** - * All of the language information can be stored in a file on the - * server-side, which DataTables will look up if this parameter is passed. - * It must store the URL of the language file, which is in a JSON format, - * and the object has the same properties as the oLanguage object in the - * initialiser object (i.e. the above parameters). Please refer to one of - * the example language files to see how this works in action. - * @type string - * @default <i>Empty string - i.e. disabled</i> - * - * @dtopt Language - * @name DataTable.defaults.language.url - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt" - * } - * } ); - * } ); - */ - "sUrl": "", - - - /** - * Text shown inside the table records when the is no information to be - * displayed after filtering. `emptyTable` is shown when there is simply no - * information in the table at all (regardless of filtering). - * @type string - * @default No matching records found - * - * @dtopt Language - * @name DataTable.defaults.language.zeroRecords - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "language": { - * "zeroRecords": "No records to display" - * } - * } ); - * } ); - */ - "sZeroRecords": "No matching records found" - }, - - - /** - * This parameter allows you to have define the global filtering state at - * initialisation time. As an object the `search` parameter must be - * defined, but all other parameters are optional. When `regex` is true, - * the search string will be treated as a regular expression, when false - * (default) it will be treated as a straight string. When `smart` - * DataTables will use it's smart filtering methods (to word match at - * any point in the data), when false this will not be done. - * @namespace - * @extends DataTable.models.oSearch - * - * @dtopt Options - * @name DataTable.defaults.search - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "search": {"search": "Initial search"} - * } ); - * } ) - */ - "oSearch": $.extend( {}, DataTable.models.oSearch ), - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * By default DataTables will look for the property `data` (or `aaData` for - * compatibility with DataTables 1.9-) when obtaining data from an Ajax - * source or for server-side processing - this parameter allows that - * property to be changed. You can use Javascript dotted object notation to - * get a data source for multiple levels of nesting. - * @type string - * @default data - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.ajaxDataProp - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sAjaxDataProp": "data", - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * You can instruct DataTables to load data from an external - * source using this parameter (use aData if you want to pass data in you - * already have). Simply provide a url a JSON object can be obtained from. - * @type string - * @default null - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.ajaxSource - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sAjaxSource": null, - - - /** - * This initialisation variable allows you to specify exactly where in the - * DOM you want DataTables to inject the various controls it adds to the page - * (for example you might want the pagination controls at the top of the - * table). DIV elements (with or without a custom class) can also be added to - * aid styling. The follow syntax is used: - * <ul> - * <li>The following options are allowed: - * <ul> - * <li>'l' - Length changing</li> - * <li>'f' - Filtering input</li> - * <li>'t' - The table!</li> - * <li>'i' - Information</li> - * <li>'p' - Pagination</li> - * <li>'r' - pRocessing</li> - * </ul> - * </li> - * <li>The following constants are allowed: - * <ul> - * <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li> - * <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li> - * </ul> - * </li> - * <li>The following syntax is expected: - * <ul> - * <li>'<' and '>' - div elements</li> - * <li>'<"class" and '>' - div with a class</li> - * <li>'<"#id" and '>' - div with an ID</li> - * </ul> - * </li> - * <li>Examples: - * <ul> - * <li>'<"wrapper"flipt>'</li> - * <li>'<lf<t>ip>'</li> - * </ul> - * </li> - * </ul> - * @type string - * @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b> - * <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i> - * - * @dtopt Options - * @name DataTable.defaults.dom - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "dom": '<"top"i>rt<"bottom"flp><"clear">' - * } ); - * } ); - */ - "sDom": "lfrtip", - - - /** - * Search delay option. This will throttle full table searches that use the - * DataTables provided search input element (it does not effect calls to - * `dt-api search()`, providing a delay before the search is made. - * @type integer - * @default 0 - * - * @dtopt Options - * @name DataTable.defaults.searchDelay - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "searchDelay": 200 - * } ); - * } ) - */ - "searchDelay": null, - - - /** - * DataTables features six different built-in options for the buttons to - * display for pagination control: - * - * * `numbers` - Page number buttons only - * * `simple` - 'Previous' and 'Next' buttons only - * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers - * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons - * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers - * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers - * - * Further methods can be added using {@link DataTable.ext.oPagination}. - * @type string - * @default simple_numbers - * - * @dtopt Options - * @name DataTable.defaults.pagingType - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "pagingType": "full_numbers" - * } ); - * } ) - */ - "sPaginationType": "simple_numbers", - - - /** - * Enable horizontal scrolling. When a table is too wide to fit into a - * certain layout, or you have a large number of columns in the table, you - * can enable x-scrolling to show the table in a viewport, which can be - * scrolled. This property can be `true` which will allow the table to - * scroll horizontally when needed, or any CSS unit, or a number (in which - * case it will be treated as a pixel measurement). Setting as simply `true` - * is recommended. - * @type boolean|string - * @default <i>blank string - i.e. disabled</i> - * - * @dtopt Features - * @name DataTable.defaults.scrollX - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollX": true, - * "scrollCollapse": true - * } ); - * } ); - */ - "sScrollX": "", - - - /** - * This property can be used to force a DataTable to use more width than it - * might otherwise do when x-scrolling is enabled. For example if you have a - * table which requires to be well spaced, this parameter is useful for - * "over-sizing" the table, and thus forcing scrolling. This property can by - * any CSS unit, or a number (in which case it will be treated as a pixel - * measurement). - * @type string - * @default <i>blank string - i.e. disabled</i> - * - * @dtopt Options - * @name DataTable.defaults.scrollXInner - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollX": "100%", - * "scrollXInner": "110%" - * } ); - * } ); - */ - "sScrollXInner": "", - - - /** - * Enable vertical scrolling. Vertical scrolling will constrain the DataTable - * to the given height, and enable scrolling for any data which overflows the - * current viewport. This can be used as an alternative to paging to display - * a lot of data in a small area (although paging and scrolling can both be - * enabled at the same time). This property can be any CSS unit, or a number - * (in which case it will be treated as a pixel measurement). - * @type string - * @default <i>blank string - i.e. disabled</i> - * - * @dtopt Features - * @name DataTable.defaults.scrollY - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "scrollY": "200px", - * "paginate": false - * } ); - * } ); - */ - "sScrollY": "", - - - /** - * __Deprecated__ The functionality provided by this parameter has now been - * superseded by that provided through `ajax`, which should be used instead. - * - * Set the HTTP method that is used to make the Ajax call for server-side - * processing or Ajax sourced data. - * @type string - * @default GET - * - * @dtopt Options - * @dtopt Server-side - * @name DataTable.defaults.serverMethod - * - * @deprecated 1.10. Please use `ajax` for this functionality now. - */ - "sServerMethod": "GET", - - - /** - * DataTables makes use of renderers when displaying HTML elements for - * a table. These renderers can be added or modified by plug-ins to - * generate suitable mark-up for a site. For example the Bootstrap - * integration plug-in for DataTables uses a paging button renderer to - * display pagination buttons in the mark-up required by Bootstrap. - * - * For further information about the renderers available see - * DataTable.ext.renderer - * @type string|object - * @default null - * - * @name DataTable.defaults.renderer - * - */ - "renderer": null, - - - /** - * Set the data property name that DataTables should use to get a row's id - * to set as the `id` property in the node. - * @type string - * @default DT_RowId - * - * @name DataTable.defaults.rowId - */ - "rowId": "DT_RowId" - }; - - _fnHungarianMap( DataTable.defaults ); - - - - /* - * Developer note - See note in model.defaults.js about the use of Hungarian - * notation and camel case. - */ - - /** - * Column options that can be given to DataTables at initialisation time. - * @namespace - */ - DataTable.defaults.column = { - /** - * Define which column(s) an order will occur on for this column. This - * allows a column's ordering to take multiple columns into account when - * doing a sort or use the data from a different column. For example first - * name / last name columns make sense to do a multi-column sort over the - * two columns. - * @type array|int - * @default null <i>Takes the value of the column index automatically</i> - * - * @name DataTable.defaults.column.orderData - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderData": [ 0, 1 ], "targets": [ 0 ] }, - * { "orderData": [ 1, 0 ], "targets": [ 1 ] }, - * { "orderData": 2, "targets": [ 2 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "orderData": [ 0, 1 ] }, - * { "orderData": [ 1, 0 ] }, - * { "orderData": 2 }, - * null, - * null - * ] - * } ); - * } ); - */ - "aDataSort": null, - "iDataSort": -1, - - - /** - * You can control the default ordering direction, and even alter the - * behaviour of the sort handler (i.e. only allow ascending ordering etc) - * using this parameter. - * @type array - * @default [ 'asc', 'desc' ] - * - * @name DataTable.defaults.column.orderSequence - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderSequence": [ "asc" ], "targets": [ 1 ] }, - * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] }, - * { "orderSequence": [ "desc" ], "targets": [ 3 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * { "orderSequence": [ "asc" ] }, - * { "orderSequence": [ "desc", "asc", "asc" ] }, - * { "orderSequence": [ "desc" ] }, - * null - * ] - * } ); - * } ); - */ - "asSorting": [ 'asc', 'desc' ], - - - /** - * Enable or disable filtering on the data in this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.searchable - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "searchable": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "searchable": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bSearchable": true, - - - /** - * Enable or disable ordering on this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.orderable - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderable": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "orderable": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bSortable": true, - - - /** - * Enable or disable the display of this column. - * @type boolean - * @default true - * - * @name DataTable.defaults.column.visible - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "visible": false, "targets": [ 0 ] } - * ] } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "visible": false }, - * null, - * null, - * null, - * null - * ] } ); - * } ); - */ - "bVisible": true, - - - /** - * Developer definable function that is called whenever a cell is created (Ajax source, - * etc) or processed for input (DOM source). This can be used as a compliment to mRender - * allowing you to modify the DOM element (add background colour for example) when the - * element is available. - * @type function - * @param {element} td The TD node that has been created - * @param {*} cellData The Data for the cell - * @param {array|object} rowData The data for the whole row - * @param {int} row The row index for the aoData data store - * @param {int} col The column index for aoColumns - * - * @name DataTable.defaults.column.createdCell - * @dtopt Columns - * - * @example - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [3], - * "createdCell": function (td, cellData, rowData, row, col) { - * if ( cellData == "1.7" ) { - * $(td).css('color', 'blue') - * } - * } - * } ] - * }); - * } ); - */ - "fnCreatedCell": null, - - - /** - * This parameter has been replaced by `data` in DataTables to ensure naming - * consistency. `dataProp` can still be used, as there is backwards - * compatibility in DataTables for this option, but it is strongly - * recommended that you use `data` in preference to `dataProp`. - * @name DataTable.defaults.column.dataProp - */ - - - /** - * This property can be used to read data from any data source property, - * including deeply nested objects / properties. `data` can be given in a - * number of different ways which effect its behaviour: - * - * * `integer` - treated as an array index for the data source. This is the - * default that DataTables uses (incrementally increased for each column). - * * `string` - read an object property from the data source. There are - * three 'special' options that can be used in the string to alter how - * DataTables reads the data from the source object: - * * `.` - Dotted Javascript notation. Just as you use a `.` in - * Javascript to read from nested objects, so to can the options - * specified in `data`. For example: `browser.version` or - * `browser.name`. If your object parameter name contains a period, use - * `\\` to escape it - i.e. `first\\.name`. - * * `[]` - Array notation. DataTables can automatically combine data - * from and array source, joining the data with the characters provided - * between the two brackets. For example: `name[, ]` would provide a - * comma-space separated list from the source array. If no characters - * are provided between the brackets, the original array source is - * returned. - * * `()` - Function notation. Adding `()` to the end of a parameter will - * execute a function of the name given. For example: `browser()` for a - * simple function on the data source, `browser.version()` for a - * function in a nested property or even `browser().version` to get an - * object property if the function called returns an object. Note that - * function notation is recommended for use in `render` rather than - * `data` as it is much simpler to use as a renderer. - * * `null` - use the original data source for the row rather than plucking - * data directly from it. This action has effects on two other - * initialisation options: - * * `defaultContent` - When null is given as the `data` option and - * `defaultContent` is specified for the column, the value defined by - * `defaultContent` will be used for the cell. - * * `render` - When null is used for the `data` option and the `render` - * option is specified for the column, the whole data source for the - * row is used for the renderer. - * * `function` - the function given will be executed whenever DataTables - * needs to set or get the data for a cell in the column. The function - * takes three parameters: - * * Parameters: - * * `{array|object}` The data source for the row - * * `{string}` The type call data requested - this will be 'set' when - * setting data or 'filter', 'display', 'type', 'sort' or undefined - * when gathering data. Note that when `undefined` is given for the - * type DataTables expects to get the raw data for the object back< - * * `{*}` Data to set when the second parameter is 'set'. - * * Return: - * * The return value from the function is not required when 'set' is - * the type of call, but otherwise the return is what will be used - * for the data requested. - * - * Note that `data` is a getter and setter option. If you just require - * formatting of data for output, you will likely want to use `render` which - * is simply a getter and thus simpler to use. - * - * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The - * name change reflects the flexibility of this property and is consistent - * with the naming of mRender. If 'mDataProp' is given, then it will still - * be used by DataTables, as it automatically maps the old name to the new - * if required. - * - * @type string|int|function|null - * @default null <i>Use automatically calculated column index</i> - * - * @name DataTable.defaults.column.data - * @dtopt Columns - * - * @example - * // Read table data from objects - * // JSON structure for each row: - * // { - * // "engine": {value}, - * // "browser": {value}, - * // "platform": {value}, - * // "version": {value}, - * // "grade": {value} - * // } - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/objects.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { "data": "platform" }, - * { "data": "version" }, - * { "data": "grade" } - * ] - * } ); - * } ); - * - * @example - * // Read information from deeply nested objects - * // JSON structure for each row: - * // { - * // "engine": {value}, - * // "browser": {value}, - * // "platform": { - * // "inner": {value} - * // }, - * // "details": [ - * // {value}, {value} - * // ] - * // } - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/deep.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { "data": "platform.inner" }, - * { "data": "details.0" }, - * { "data": "details.1" } - * ] - * } ); - * } ); - * - * @example - * // Using `data` as a function to provide different information for - * // sorting, filtering and display. In this case, currency (price) - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": function ( source, type, val ) { - * if (type === 'set') { - * source.price = val; - * // Store the computed dislay and filter values for efficiency - * source.price_display = val=="" ? "" : "$"+numberFormat(val); - * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val; - * return; - * } - * else if (type === 'display') { - * return source.price_display; - * } - * else if (type === 'filter') { - * return source.price_filter; - * } - * // 'sort', 'type' and undefined all just use the integer - * return source.price; - * } - * } ] - * } ); - * } ); - * - * @example - * // Using default content - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, - * "defaultContent": "Click to edit" - * } ] - * } ); - * } ); - * - * @example - * // Using array notation - outputting a list from an array - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": "name[, ]" - * } ] - * } ); - * } ); - * - */ - "mData": null, - - - /** - * This property is the rendering partner to `data` and it is suggested that - * when you want to manipulate data for display (including filtering, - * sorting etc) without altering the underlying data for the table, use this - * property. `render` can be considered to be the the read only companion to - * `data` which is read / write (then as such more complex). Like `data` - * this option can be given in a number of different ways to effect its - * behaviour: - * - * * `integer` - treated as an array index for the data source. This is the - * default that DataTables uses (incrementally increased for each column). - * * `string` - read an object property from the data source. There are - * three 'special' options that can be used in the string to alter how - * DataTables reads the data from the source object: - * * `.` - Dotted Javascript notation. Just as you use a `.` in - * Javascript to read from nested objects, so to can the options - * specified in `data`. For example: `browser.version` or - * `browser.name`. If your object parameter name contains a period, use - * `\\` to escape it - i.e. `first\\.name`. - * * `[]` - Array notation. DataTables can automatically combine data - * from and array source, joining the data with the characters provided - * between the two brackets. For example: `name[, ]` would provide a - * comma-space separated list from the source array. If no characters - * are provided between the brackets, the original array source is - * returned. - * * `()` - Function notation. Adding `()` to the end of a parameter will - * execute a function of the name given. For example: `browser()` for a - * simple function on the data source, `browser.version()` for a - * function in a nested property or even `browser().version` to get an - * object property if the function called returns an object. - * * `object` - use different data for the different data types requested by - * DataTables ('filter', 'display', 'type' or 'sort'). The property names - * of the object is the data type the property refers to and the value can - * defined using an integer, string or function using the same rules as - * `render` normally does. Note that an `_` option _must_ be specified. - * This is the default value to use if you haven't specified a value for - * the data type requested by DataTables. - * * `function` - the function given will be executed whenever DataTables - * needs to set or get the data for a cell in the column. The function - * takes three parameters: - * * Parameters: - * * {array|object} The data source for the row (based on `data`) - * * {string} The type call data requested - this will be 'filter', - * 'display', 'type' or 'sort'. - * * {array|object} The full data source for the row (not based on - * `data`) - * * Return: - * * The return value from the function is what will be used for the - * data requested. - * - * @type string|int|function|object|null - * @default null Use the data source value. - * - * @name DataTable.defaults.column.render - * @dtopt Columns - * - * @example - * // Create a comma separated list from an array of objects - * $(document).ready( function() { - * $('#example').dataTable( { - * "ajaxSource": "sources/deep.txt", - * "columns": [ - * { "data": "engine" }, - * { "data": "browser" }, - * { - * "data": "platform", - * "render": "[, ].name" - * } - * ] - * } ); - * } ); - * - * @example - * // Execute a function to obtain data - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, // Use the full data source object for the renderer's source - * "render": "browserName()" - * } ] - * } ); - * } ); - * - * @example - * // As an object, extracting different data for the different types - * // This would be used with a data source such as: - * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" } - * // Here the `phone` integer is used for sorting and type detection, while `phone_filter` - * // (which has both forms) is used for filtering for if a user inputs either format, while - * // the formatted phone number is the one that is shown in the table. - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": null, // Use the full data source object for the renderer's source - * "render": { - * "_": "phone", - * "filter": "phone_filter", - * "display": "phone_display" - * } - * } ] - * } ); - * } ); - * - * @example - * // Use as a function to create a link from the data source - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "data": "download_link", - * "render": function ( data, type, full ) { - * return '<a href="'+data+'">Download</a>'; - * } - * } ] - * } ); - * } ); - */ - "mRender": null, - - - /** - * Change the cell type created for the column - either TD cells or TH cells. This - * can be useful as TH cells have semantic meaning in the table body, allowing them - * to act as a header for a row (you may wish to add scope='row' to the TH elements). - * @type string - * @default td - * - * @name DataTable.defaults.column.cellType - * @dtopt Columns - * - * @example - * // Make the first column use TH cells - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ { - * "targets": [ 0 ], - * "cellType": "th" - * } ] - * } ); - * } ); - */ - "sCellType": "td", - - - /** - * Class to give to each cell in this column. - * @type string - * @default <i>Empty string</i> - * - * @name DataTable.defaults.column.class - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "class": "my_class", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "class": "my_class" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sClass": "", - - /** - * When DataTables calculates the column widths to assign to each column, - * it finds the longest string in each column and then constructs a - * temporary table and reads the widths from that. The problem with this - * is that "mmm" is much wider then "iiii", but the latter is a longer - * string - thus the calculation can go wrong (doing it properly and putting - * it into an DOM object and measuring that is horribly(!) slow). Thus as - * a "work around" we provide this option. It will append its value to the - * text that is found to be the longest string for the column - i.e. padding. - * Generally you shouldn't need this! - * @type string - * @default <i>Empty string<i> - * - * @name DataTable.defaults.column.contentPadding - * @dtopt Columns - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * null, - * { - * "contentPadding": "mmm" - * } - * ] - * } ); - * } ); - */ - "sContentPadding": "", - - - /** - * Allows a default value to be given for a column's data, and will be used - * whenever a null data source is encountered (this can be because `data` - * is set to null, or because the data source itself is null). - * @type string - * @default null - * - * @name DataTable.defaults.column.defaultContent - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { - * "data": null, - * "defaultContent": "Edit", - * "targets": [ -1 ] - * } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * null, - * { - * "data": null, - * "defaultContent": "Edit" - * } - * ] - * } ); - * } ); - */ - "sDefaultContent": null, - - - /** - * This parameter is only used in DataTables' server-side processing. It can - * be exceptionally useful to know what columns are being displayed on the - * client side, and to map these to database fields. When defined, the names - * also allow DataTables to reorder information from the server if it comes - * back in an unexpected order (i.e. if you switch your columns around on the - * client-side, your server-side code does not also need updating). - * @type string - * @default <i>Empty string</i> - * - * @name DataTable.defaults.column.name - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "name": "engine", "targets": [ 0 ] }, - * { "name": "browser", "targets": [ 1 ] }, - * { "name": "platform", "targets": [ 2 ] }, - * { "name": "version", "targets": [ 3 ] }, - * { "name": "grade", "targets": [ 4 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "name": "engine" }, - * { "name": "browser" }, - * { "name": "platform" }, - * { "name": "version" }, - * { "name": "grade" } - * ] - * } ); - * } ); - */ - "sName": "", - - - /** - * Defines a data source type for the ordering which can be used to read - * real-time information from the table (updating the internally cached - * version) prior to ordering. This allows ordering to occur on user - * editable elements such as form inputs. - * @type string - * @default std - * - * @name DataTable.defaults.column.orderDataType - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "orderDataType": "dom-text", "targets": [ 2, 3 ] }, - * { "type": "numeric", "targets": [ 3 ] }, - * { "orderDataType": "dom-select", "targets": [ 4 ] }, - * { "orderDataType": "dom-checkbox", "targets": [ 5 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * null, - * null, - * { "orderDataType": "dom-text" }, - * { "orderDataType": "dom-text", "type": "numeric" }, - * { "orderDataType": "dom-select" }, - * { "orderDataType": "dom-checkbox" } - * ] - * } ); - * } ); - */ - "sSortDataType": "std", - - - /** - * The title of this column. - * @type string - * @default null <i>Derived from the 'TH' value for this column in the - * original HTML table.</i> - * - * @name DataTable.defaults.column.title - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "title": "My column title", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "title": "My column title" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sTitle": null, - - - /** - * The type allows you to specify how the data for this column will be - * ordered. Four types (string, numeric, date and html (which will strip - * HTML tags before ordering)) are currently available. Note that only date - * formats understood by Javascript's Date() object will be accepted as type - * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', - * 'numeric', 'date' or 'html' (by default). Further types can be adding - * through plug-ins. - * @type string - * @default null <i>Auto-detected from raw data</i> - * - * @name DataTable.defaults.column.type - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "type": "html", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "type": "html" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sType": null, - - - /** - * Defining the width of the column, this parameter may take any CSS value - * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not - * been given a specific width through this interface ensuring that the table - * remains readable. - * @type string - * @default null <i>Automatic</i> - * - * @name DataTable.defaults.column.width - * @dtopt Columns - * - * @example - * // Using `columnDefs` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columnDefs": [ - * { "width": "20%", "targets": [ 0 ] } - * ] - * } ); - * } ); - * - * @example - * // Using `columns` - * $(document).ready( function() { - * $('#example').dataTable( { - * "columns": [ - * { "width": "20%" }, - * null, - * null, - * null, - * null - * ] - * } ); - * } ); - */ - "sWidth": null - }; - - _fnHungarianMap( DataTable.defaults.column ); - - - - /** - * DataTables settings object - this holds all the information needed for a - * given table, including configuration, data and current application of the - * table options. DataTables does not have a single instance for each DataTable - * with the settings attached to that instance, but rather instances of the - * DataTable "class" are created on-the-fly as needed (typically by a - * $().dataTable() call) and the settings object is then applied to that - * instance. - * - * Note that this object is related to {@link DataTable.defaults} but this - * one is the internal data store for DataTables's cache of columns. It should - * NOT be manipulated outside of DataTables. Any configuration should be done - * through the initialisation options. - * @namespace - * @todo Really should attach the settings object to individual instances so we - * don't need to create new instances on each $().dataTable() call (if the - * table already exists). It would also save passing oSettings around and - * into every single function. However, this is a very significant - * architecture change for DataTables and will almost certainly break - * backwards compatibility with older installations. This is something that - * will be done in 2.0. - */ - DataTable.models.oSettings = { - /** - * Primary features of DataTables and their enablement state. - * @namespace - */ - "oFeatures": { - - /** - * Flag to say if DataTables should automatically try to calculate the - * optimum table and columns widths (true) or not (false). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bAutoWidth": null, - - /** - * Delay the creation of TR and TD elements until they are actually - * needed by a driven page draw. This can give a significant speed - * increase for Ajax source and Javascript source data, but makes no - * difference at all fro DOM and server-side processing tables. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bDeferRender": null, - - /** - * Enable filtering on the table or not. Note that if this is disabled - * then there is no filtering at all on the table, including fnFilter. - * To just remove the filtering input use sDom and remove the 'f' option. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bFilter": null, - - /** - * Table information element (the 'Showing x of y records' div) enable - * flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bInfo": null, - - /** - * Present a user control allowing the end user to change the page size - * when pagination is enabled. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bLengthChange": null, - - /** - * Pagination enabled or not. Note that if this is disabled then length - * changing must also be disabled. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bPaginate": null, - - /** - * Processing indicator enable flag whenever DataTables is enacting a - * user request - typically an Ajax request for server-side processing. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bProcessing": null, - - /** - * Server-side processing enabled flag - when enabled DataTables will - * get all data from the server for every draw - there is no filtering, - * sorting or paging done on the client-side. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bServerSide": null, - - /** - * Sorting enablement flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSort": null, - - /** - * Multi-column sorting - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSortMulti": null, - - /** - * Apply a class to the columns which are being sorted to provide a - * visual highlight or not. This can slow things down when enabled since - * there is a lot of DOM interaction. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSortClasses": null, - - /** - * State saving enablement flag. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bStateSave": null - }, - - - /** - * Scrolling settings for a table. - * @namespace - */ - "oScroll": { - /** - * When the table is shorter in height than sScrollY, collapse the - * table container down to the height of the table (when true). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bCollapse": null, - - /** - * Width of the scrollbar for the web-browser's platform. Calculated - * during table initialisation. - * @type int - * @default 0 - */ - "iBarWidth": 0, - - /** - * Viewport width for horizontal scrolling. Horizontal scrolling is - * disabled if an empty string. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - */ - "sX": null, - - /** - * Width to expand the table to when using x-scrolling. Typically you - * should not need to use this. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @deprecated - */ - "sXInner": null, - - /** - * Viewport height for vertical scrolling. Vertical scrolling is disabled - * if an empty string. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - */ - "sY": null - }, - - /** - * Language information for the table. - * @namespace - * @extends DataTable.defaults.oLanguage - */ - "oLanguage": { - /** - * Information callback function. See - * {@link DataTable.defaults.fnInfoCallback} - * @type function - * @default null - */ - "fnInfoCallback": null - }, - - /** - * Browser support parameters - * @namespace - */ - "oBrowser": { - /** - * Indicate if the browser incorrectly calculates width:100% inside a - * scrolling element (IE6/7) - * @type boolean - * @default false - */ - "bScrollOversize": false, - - /** - * Determine if the vertical scrollbar is on the right or left of the - * scrolling container - needed for rtl language layout, although not - * all browsers move the scrollbar (Safari). - * @type boolean - * @default false - */ - "bScrollbarLeft": false, - - /** - * Flag for if `getBoundingClientRect` is fully supported or not - * @type boolean - * @default false - */ - "bBounding": false, - - /** - * Browser scrollbar width - * @type integer - * @default 0 - */ - "barWidth": 0 - }, - - - "ajax": null, - - - /** - * Array referencing the nodes which are used for the features. The - * parameters of this object match what is allowed by sDom - i.e. - * <ul> - * <li>'l' - Length changing</li> - * <li>'f' - Filtering input</li> - * <li>'t' - The table!</li> - * <li>'i' - Information</li> - * <li>'p' - Pagination</li> - * <li>'r' - pRocessing</li> - * </ul> - * @type array - * @default [] - */ - "aanFeatures": [], - - /** - * Store data information - see {@link DataTable.models.oRow} for detailed - * information. - * @type array - * @default [] - */ - "aoData": [], - - /** - * Array of indexes which are in the current display (after filtering etc) - * @type array - * @default [] - */ - "aiDisplay": [], - - /** - * Array of indexes for display - no filtering - * @type array - * @default [] - */ - "aiDisplayMaster": [], - - /** - * Map of row ids to data indexes - * @type object - * @default {} - */ - "aIds": {}, - - /** - * Store information about each column that is in use - * @type array - * @default [] - */ - "aoColumns": [], - - /** - * Store information about the table's header - * @type array - * @default [] - */ - "aoHeader": [], - - /** - * Store information about the table's footer - * @type array - * @default [] - */ - "aoFooter": [], - - /** - * Store the applied global search information in case we want to force a - * research or compare the old search to a new one. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @namespace - * @extends DataTable.models.oSearch - */ - "oPreviousSearch": {}, - - /** - * Store the applied search for each column - see - * {@link DataTable.models.oSearch} for the format that is used for the - * filtering information for each column. - * @type array - * @default [] - */ - "aoPreSearchCols": [], - - /** - * Sorting that is applied to the table. Note that the inner arrays are - * used in the following manner: - * <ul> - * <li>Index 0 - column number</li> - * <li>Index 1 - current sorting direction</li> - * </ul> - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @todo These inner arrays should really be objects - */ - "aaSorting": null, - - /** - * Sorting that is always applied to the table (i.e. prefixed in front of - * aaSorting). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "aaSortingFixed": [], - - /** - * Classes to use for the striping of a table. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "asStripeClasses": null, - - /** - * If restoring a table - we should restore its striping classes as well - * @type array - * @default [] - */ - "asDestroyStripes": [], - - /** - * If restoring a table - we should restore its width - * @type int - * @default 0 - */ - "sDestroyWidth": 0, - - /** - * Callback functions array for every time a row is inserted (i.e. on a draw). - * @type array - * @default [] - */ - "aoRowCallback": [], - - /** - * Callback functions for the header on each draw. - * @type array - * @default [] - */ - "aoHeaderCallback": [], - - /** - * Callback function for the footer on each draw. - * @type array - * @default [] - */ - "aoFooterCallback": [], - - /** - * Array of callback functions for draw callback functions - * @type array - * @default [] - */ - "aoDrawCallback": [], - - /** - * Array of callback functions for row created function - * @type array - * @default [] - */ - "aoRowCreatedCallback": [], - - /** - * Callback functions for just before the table is redrawn. A return of - * false will be used to cancel the draw. - * @type array - * @default [] - */ - "aoPreDrawCallback": [], - - /** - * Callback functions for when the table has been initialised. - * @type array - * @default [] - */ - "aoInitComplete": [], - - - /** - * Callbacks for modifying the settings to be stored for state saving, prior to - * saving state. - * @type array - * @default [] - */ - "aoStateSaveParams": [], - - /** - * Callbacks for modifying the settings that have been stored for state saving - * prior to using the stored values to restore the state. - * @type array - * @default [] - */ - "aoStateLoadParams": [], - - /** - * Callbacks for operating on the settings object once the saved state has been - * loaded - * @type array - * @default [] - */ - "aoStateLoaded": [], - - /** - * Cache the table ID for quick access - * @type string - * @default <i>Empty string</i> - */ - "sTableId": "", - - /** - * The TABLE node for the main table - * @type node - * @default null - */ - "nTable": null, - - /** - * Permanent ref to the thead element - * @type node - * @default null - */ - "nTHead": null, - - /** - * Permanent ref to the tfoot element - if it exists - * @type node - * @default null - */ - "nTFoot": null, - - /** - * Permanent ref to the tbody element - * @type node - * @default null - */ - "nTBody": null, - - /** - * Cache the wrapper node (contains all DataTables controlled elements) - * @type node - * @default null - */ - "nTableWrapper": null, - - /** - * Indicate if when using server-side processing the loading of data - * should be deferred until the second draw. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - * @default false - */ - "bDeferLoading": false, - - /** - * Indicate if all required information has been read in - * @type boolean - * @default false - */ - "bInitialised": false, - - /** - * Information about open rows. Each object in the array has the parameters - * 'nTr' and 'nParent' - * @type array - * @default [] - */ - "aoOpenRows": [], - - /** - * Dictate the positioning of DataTables' control elements - see - * {@link DataTable.model.oInit.sDom}. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @default null - */ - "sDom": null, - - /** - * Search delay (in mS) - * @type integer - * @default null - */ - "searchDelay": null, - - /** - * Which type of pagination should be used. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @default two_button - */ - "sPaginationType": "two_button", - - /** - * The state duration (for `stateSave`) in seconds. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type int - * @default 0 - */ - "iStateDuration": 0, - - /** - * Array of callback functions for state saving. Each array element is an - * object with the following parameters: - * <ul> - * <li>function:fn - function to call. Takes two parameters, oSettings - * and the JSON string to save that has been thus far created. Returns - * a JSON string to be inserted into a json object - * (i.e. '"param": [ 0, 1, 2]')</li> - * <li>string:sName - name of callback</li> - * </ul> - * @type array - * @default [] - */ - "aoStateSave": [], - - /** - * Array of callback functions for state loading. Each array element is an - * object with the following parameters: - * <ul> - * <li>function:fn - function to call. Takes two parameters, oSettings - * and the object stored. May return false to cancel state loading</li> - * <li>string:sName - name of callback</li> - * </ul> - * @type array - * @default [] - */ - "aoStateLoad": [], - - /** - * State that was saved. Useful for back reference - * @type object - * @default null - */ - "oSavedState": null, - - /** - * State that was loaded. Useful for back reference - * @type object - * @default null - */ - "oLoadedState": null, - - /** - * Source url for AJAX data for the table. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - * @default null - */ - "sAjaxSource": null, - - /** - * Property from a given object from which to read the table data from. This - * can be an empty string (when not server-side processing), in which case - * it is assumed an an array is given directly. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - */ - "sAjaxDataProp": null, - - /** - * Note if draw should be blocked while getting data - * @type boolean - * @default true - */ - "bAjaxDataGet": true, - - /** - * The last jQuery XHR object that was used for server-side data gathering. - * This can be used for working with the XHR information in one of the - * callbacks - * @type object - * @default null - */ - "jqXHR": null, - - /** - * JSON returned from the server in the last Ajax request - * @type object - * @default undefined - */ - "json": undefined, - - /** - * Data submitted as part of the last Ajax request - * @type object - * @default undefined - */ - "oAjaxData": undefined, - - /** - * Function to get the server-side data. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type function - */ - "fnServerData": null, - - /** - * Functions which are called prior to sending an Ajax request so extra - * parameters can easily be sent to the server - * @type array - * @default [] - */ - "aoServerParams": [], - - /** - * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if - * required). - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type string - */ - "sServerMethod": null, - - /** - * Format numbers for display. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type function - */ - "fnFormatNumber": null, - - /** - * List of options that can be used for the user selectable length menu. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type array - * @default [] - */ - "aLengthMenu": null, - - /** - * Counter for the draws that the table does. Also used as a tracker for - * server-side processing - * @type int - * @default 0 - */ - "iDraw": 0, - - /** - * Indicate if a redraw is being done - useful for Ajax - * @type boolean - * @default false - */ - "bDrawing": false, - - /** - * Draw index (iDraw) of the last error when parsing the returned data - * @type int - * @default -1 - */ - "iDrawError": -1, - - /** - * Paging display length - * @type int - * @default 10 - */ - "_iDisplayLength": 10, - - /** - * Paging start point - aiDisplay index - * @type int - * @default 0 - */ - "_iDisplayStart": 0, - - /** - * Server-side processing - number of records in the result set - * (i.e. before filtering), Use fnRecordsTotal rather than - * this property to get the value of the number of records, regardless of - * the server-side processing setting. - * @type int - * @default 0 - * @private - */ - "_iRecordsTotal": 0, - - /** - * Server-side processing - number of records in the current display set - * (i.e. after filtering). Use fnRecordsDisplay rather than - * this property to get the value of the number of records, regardless of - * the server-side processing setting. - * @type boolean - * @default 0 - * @private - */ - "_iRecordsDisplay": 0, - - /** - * The classes to use for the table - * @type object - * @default {} - */ - "oClasses": {}, - - /** - * Flag attached to the settings object so you can check in the draw - * callback if filtering has been done in the draw. Deprecated in favour of - * events. - * @type boolean - * @default false - * @deprecated - */ - "bFiltered": false, - - /** - * Flag attached to the settings object so you can check in the draw - * callback if sorting has been done in the draw. Deprecated in favour of - * events. - * @type boolean - * @default false - * @deprecated - */ - "bSorted": false, - - /** - * Indicate that if multiple rows are in the header and there is more than - * one unique cell per column, if the top one (true) or bottom one (false) - * should be used for sorting / title by DataTables. - * Note that this parameter will be set by the initialisation routine. To - * set a default use {@link DataTable.defaults}. - * @type boolean - */ - "bSortCellsTop": null, - - /** - * Initialisation object that is used for the table - * @type object - * @default null - */ - "oInit": null, - - /** - * Destroy callback functions - for plug-ins to attach themselves to the - * destroy so they can clean up markup and events. - * @type array - * @default [] - */ - "aoDestroyCallback": [], - - - /** - * Get the number of records in the current record set, before filtering - * @type function - */ - "fnRecordsTotal": function () - { - return _fnDataSource( this ) == 'ssp' ? - this._iRecordsTotal * 1 : - this.aiDisplayMaster.length; - }, - - /** - * Get the number of records in the current record set, after filtering - * @type function - */ - "fnRecordsDisplay": function () - { - return _fnDataSource( this ) == 'ssp' ? - this._iRecordsDisplay * 1 : - this.aiDisplay.length; - }, - - /** - * Get the display end point - aiDisplay index - * @type function - */ - "fnDisplayEnd": function () - { - var - len = this._iDisplayLength, - start = this._iDisplayStart, - calc = start + len, - records = this.aiDisplay.length, - features = this.oFeatures, - paginate = features.bPaginate; - - if ( features.bServerSide ) { - return paginate === false || len === -1 ? - start + records : - Math.min( start+len, this._iRecordsDisplay ); - } - else { - return ! paginate || calc>records || len===-1 ? - records : - calc; - } - }, - - /** - * The DataTables object for this table - * @type object - * @default null - */ - "oInstance": null, - - /** - * Unique identifier for each instance of the DataTables object. If there - * is an ID on the table node, then it takes that value, otherwise an - * incrementing internal counter is used. - * @type string - * @default null - */ - "sInstance": null, - - /** - * tabindex attribute value that is added to DataTables control elements, allowing - * keyboard navigation of the table and its controls. - */ - "iTabIndex": 0, - - /** - * DIV container for the footer scrolling table if scrolling - */ - "nScrollHead": null, - - /** - * DIV container for the footer scrolling table if scrolling - */ - "nScrollFoot": null, - - /** - * Last applied sort - * @type array - * @default [] - */ - "aLastSort": [], - - /** - * Stored plug-in instances - * @type object - * @default {} - */ - "oPlugins": {}, - - /** - * Function used to get a row's id from the row's data - * @type function - * @default null - */ - "rowIdFn": null, - - /** - * Data location where to store a row's id - * @type string - * @default null - */ - "rowId": null - }; - - /** - * Extension object for DataTables that is used to provide all extension - * options. - * - * Note that the `DataTable.ext` object is available through - * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is - * also aliased to `jQuery.fn.dataTableExt` for historic reasons. - * @namespace - * @extends DataTable.models.ext - */ - - - /** - * DataTables extensions - * - * This namespace acts as a collection area for plug-ins that can be used to - * extend DataTables capabilities. Indeed many of the build in methods - * use this method to provide their own capabilities (sorting methods for - * example). - * - * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy - * reasons - * - * @namespace - */ - DataTable.ext = _ext = { - /** - * Buttons. For use with the Buttons extension for DataTables. This is - * defined here so other extensions can define buttons regardless of load - * order. It is _not_ used by DataTables core. - * - * @type object - * @default {} - */ - buttons: {}, - - - /** - * Element class names - * - * @type object - * @default {} - */ - classes: {}, - - - /** - * DataTables build type (expanded by the download builder) - * - * @type string - */ - builder: "-source-", - - - /** - * Error reporting. - * - * How should DataTables report an error. Can take the value 'alert', - * 'throw', 'none' or a function. - * - * @type string|function - * @default alert - */ - errMode: "alert", - - - /** - * Feature plug-ins. - * - * This is an array of objects which describe the feature plug-ins that are - * available to DataTables. These feature plug-ins are then available for - * use through the `dom` initialisation option. - * - * Each feature plug-in is described by an object which must have the - * following properties: - * - * * `fnInit` - function that is used to initialise the plug-in, - * * `cFeature` - a character so the feature can be enabled by the `dom` - * instillation option. This is case sensitive. - * - * The `fnInit` function has the following input parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * - * And the following return is expected: - * - * * {node|null} The element which contains your feature. Note that the - * return may also be void if your plug-in does not require to inject any - * DOM elements into DataTables control (`dom`) - for example this might - * be useful when developing a plug-in which allows table control via - * keyboard entry - * - * @type array - * - * @example - * $.fn.dataTable.ext.features.push( { - * "fnInit": function( oSettings ) { - * return new TableTools( { "oDTSettings": oSettings } ); - * }, - * "cFeature": "T" - * } ); - */ - feature: [], - - - /** - * Row searching. - * - * This method of searching is complimentary to the default type based - * searching, and a lot more comprehensive as it allows you complete control - * over the searching logic. Each element in this array is a function - * (parameters described below) that is called for every row in the table, - * and your logic decides if it should be included in the searching data set - * or not. - * - * Searching functions have the following input parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{array|object}` Data for the row to be processed (same as the - * original format that was passed in as the data source, or an array - * from a DOM data source - * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which - * can be useful to retrieve the `TR` element if you need DOM interaction. - * - * And the following return is expected: - * - * * {boolean} Include the row in the searched result set (true) or not - * (false) - * - * Note that as with the main search ability in DataTables, technically this - * is "filtering", since it is subtractive. However, for consistency in - * naming we call it searching here. - * - * @type array - * @default [] - * - * @example - * // The following example shows custom search being applied to the - * // fourth column (i.e. the data[3] index) based on two input values - * // from the end-user, matching the data in a certain range. - * $.fn.dataTable.ext.search.push( - * function( settings, data, dataIndex ) { - * var min = document.getElementById('min').value * 1; - * var max = document.getElementById('max').value * 1; - * var version = data[3] == "-" ? 0 : data[3]*1; - * - * if ( min == "" && max == "" ) { - * return true; - * } - * else if ( min == "" && version < max ) { - * return true; - * } - * else if ( min < version && "" == max ) { - * return true; - * } - * else if ( min < version && version < max ) { - * return true; - * } - * return false; - * } - * ); - */ - search: [], - - - /** - * Selector extensions - * - * The `selector` option can be used to extend the options available for the - * selector modifier options (`selector-modifier` object data type) that - * each of the three built in selector types offer (row, column and cell + - * their plural counterparts). For example the Select extension uses this - * mechanism to provide an option to select only rows, columns and cells - * that have been marked as selected by the end user (`{selected: true}`), - * which can be used in conjunction with the existing built in selector - * options. - * - * Each property is an array to which functions can be pushed. The functions - * take three attributes: - * - * * Settings object for the host table - * * Options object (`selector-modifier` object type) - * * Array of selected item indexes - * - * The return is an array of the resulting item indexes after the custom - * selector has been applied. - * - * @type object - */ - selector: { - cell: [], - column: [], - row: [] - }, - - - /** - * Internal functions, exposed for used in plug-ins. - * - * Please note that you should not need to use the internal methods for - * anything other than a plug-in (and even then, try to avoid if possible). - * The internal function may change between releases. - * - * @type object - * @default {} - */ - internal: {}, - - - /** - * Legacy configuration options. Enable and disable legacy options that - * are available in DataTables. - * - * @type object - */ - legacy: { - /** - * Enable / disable DataTables 1.9 compatible server-side processing - * requests - * - * @type boolean - * @default null - */ - ajax: null - }, - - - /** - * Pagination plug-in methods. - * - * Each entry in this object is a function and defines which buttons should - * be shown by the pagination rendering method that is used for the table: - * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the - * buttons are displayed in the document, while the functions here tell it - * what buttons to display. This is done by returning an array of button - * descriptions (what each button will do). - * - * Pagination types (the four built in options and any additional plug-in - * options defined here) can be used through the `paginationType` - * initialisation parameter. - * - * The functions defined take two parameters: - * - * 1. `{int} page` The current page index - * 2. `{int} pages` The number of pages in the table - * - * Each function is expected to return an array where each element of the - * array can be one of: - * - * * `first` - Jump to first page when activated - * * `last` - Jump to last page when activated - * * `previous` - Show previous page when activated - * * `next` - Show next page when activated - * * `{int}` - Show page of the index given - * * `{array}` - A nested array containing the above elements to add a - * containing 'DIV' element (might be useful for styling). - * - * Note that DataTables v1.9- used this object slightly differently whereby - * an object with two functions would be defined for each plug-in. That - * ability is still supported by DataTables 1.10+ to provide backwards - * compatibility, but this option of use is now decremented and no longer - * documented in DataTables 1.10+. - * - * @type object - * @default {} - * - * @example - * // Show previous, next and current page buttons only - * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { - * return [ 'previous', page, 'next' ]; - * }; - */ - pager: {}, - - - renderer: { - pageButton: {}, - header: {} - }, - - - /** - * Ordering plug-ins - custom data source - * - * The extension options for ordering of data available here is complimentary - * to the default type based ordering that DataTables typically uses. It - * allows much greater control over the the data that is being used to - * order a column, but is necessarily therefore more complex. - * - * This type of ordering is useful if you want to do ordering based on data - * live from the DOM (for example the contents of an 'input' element) rather - * than just the static string that DataTables knows of. - * - * The way these plug-ins work is that you create an array of the values you - * wish to be ordering for the column in question and then return that - * array. The data in the array much be in the index order of the rows in - * the table (not the currently ordering order!). Which order data gathering - * function is run here depends on the `dt-init columns.orderDataType` - * parameter that is used for the column (if any). - * - * The functions defined take two parameters: - * - * 1. `{object}` DataTables settings object: see - * {@link DataTable.models.oSettings} - * 2. `{int}` Target column index - * - * Each function is expected to return an array: - * - * * `{array}` Data for the column to be ordering upon - * - * @type array - * - * @example - * // Ordering using `input` node values - * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) - * { - * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { - * return $('input', td).val(); - * } ); - * } - */ - order: {}, - - - /** - * Type based plug-ins. - * - * Each column in DataTables has a type assigned to it, either by automatic - * detection or by direct assignment using the `type` option for the column. - * The type of a column will effect how it is ordering and search (plug-ins - * can also make use of the column type if required). - * - * @namespace - */ - type: { - /** - * Type detection functions. - * - * The functions defined in this object are used to automatically detect - * a column's type, making initialisation of DataTables super easy, even - * when complex data is in the table. - * - * The functions defined take two parameters: - * - * 1. `{*}` Data from the column cell to be analysed - * 2. `{settings}` DataTables settings object. This can be used to - * perform context specific type detection - for example detection - * based on language settings such as using a comma for a decimal - * place. Generally speaking the options from the settings will not - * be required - * - * Each function is expected to return: - * - * * `{string|null}` Data type detected, or null if unknown (and thus - * pass it on to the other type detection functions. - * - * @type array - * - * @example - * // Currency type detection plug-in: - * $.fn.dataTable.ext.type.detect.push( - * function ( data, settings ) { - * // Check the numeric part - * if ( ! data.substring(1).match(/[0-9]/) ) { - * return null; - * } - * - * // Check prefixed by currency - * if ( data.charAt(0) == '$' || data.charAt(0) == '£' ) { - * return 'currency'; - * } - * return null; - * } - * ); - */ - detect: [], - - - /** - * Type based search formatting. - * - * The type based searching functions can be used to pre-format the - * data to be search on. For example, it can be used to strip HTML - * tags or to de-format telephone numbers for numeric only searching. - * - * Note that is a search is not defined for a column of a given type, - * no search formatting will be performed. - * - * Pre-processing of searching data plug-ins - When you assign the sType - * for a column (or have it automatically detected for you by DataTables - * or a type detection plug-in), you will typically be using this for - * custom sorting, but it can also be used to provide custom searching - * by allowing you to pre-processing the data and returning the data in - * the format that should be searched upon. This is done by adding - * functions this object with a parameter name which matches the sType - * for that target column. This is the corollary of <i>afnSortData</i> - * for searching data. - * - * The functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for searching - * - * Each function is expected to return: - * - * * `{string|null}` Formatted string that will be used for the searching. - * - * @type object - * @default {} - * - * @example - * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { - * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); - * } - */ - search: {}, - - - /** - * Type based ordering. - * - * The column type tells DataTables what ordering to apply to the table - * when a column is sorted upon. The order for each type that is defined, - * is defined by the functions available in this object. - * - * Each ordering option can be described by three properties added to - * this object: - * - * * `{type}-pre` - Pre-formatting function - * * `{type}-asc` - Ascending order function - * * `{type}-desc` - Descending order function - * - * All three can be used together, only `{type}-pre` or only - * `{type}-asc` and `{type}-desc` together. It is generally recommended - * that only `{type}-pre` is used, as this provides the optimal - * implementation in terms of speed, although the others are provided - * for compatibility with existing Javascript sort functions. - * - * `{type}-pre`: Functions defined take a single parameter: - * - * 1. `{*}` Data from the column cell to be prepared for ordering - * - * And return: - * - * * `{*}` Data to be sorted upon - * - * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort - * functions, taking two parameters: - * - * 1. `{*}` Data to compare to the second parameter - * 2. `{*}` Data to compare to the first parameter - * - * And returning: - * - * * `{*}` Ordering match: <0 if first parameter should be sorted lower - * than the second parameter, ===0 if the two parameters are equal and - * >0 if the first parameter should be sorted height than the second - * parameter. - * - * @type object - * @default {} - * - * @example - * // Numeric ordering of formatted numbers with a pre-formatter - * $.extend( $.fn.dataTable.ext.type.order, { - * "string-pre": function(x) { - * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); - * return parseFloat( a ); - * } - * } ); - * - * @example - * // Case-sensitive string ordering, with no pre-formatting method - * $.extend( $.fn.dataTable.ext.order, { - * "string-case-asc": function(x,y) { - * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - * }, - * "string-case-desc": function(x,y) { - * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - * } - * } ); - */ - order: {} - }, - - /** - * Unique DataTables instance counter - * - * @type int - * @private - */ - _unique: 0, - - - // - // Depreciated - // The following properties are retained for backwards compatiblity only. - // The should not be used in new projects and will be removed in a future - // version - // - - /** - * Version check function. - * @type function - * @depreciated Since 1.10 - */ - fnVersionCheck: DataTable.fnVersionCheck, - - - /** - * Index for what 'this' index API functions should use - * @type int - * @deprecated Since v1.10 - */ - iApiIndex: 0, - - - /** - * jQuery UI class container - * @type object - * @deprecated Since v1.10 - */ - oJUIClasses: {}, - - - /** - * Software version - * @type string - * @deprecated Since v1.10 - */ - sVersion: DataTable.version - }; - - - // - // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts - // - $.extend( _ext, { - afnFiltering: _ext.search, - aTypes: _ext.type.detect, - ofnSearch: _ext.type.search, - oSort: _ext.type.order, - afnSortData: _ext.order, - aoFeatures: _ext.feature, - oApi: _ext.internal, - oStdClasses: _ext.classes, - oPagination: _ext.pager - } ); - - - $.extend( DataTable.ext.classes, { - "sTable": "dataTable", - "sNoFooter": "no-footer", - - /* Paging buttons */ - "sPageButton": "paginate_button", - "sPageButtonActive": "current", - "sPageButtonDisabled": "disabled", - - /* Striping classes */ - "sStripeOdd": "odd", - "sStripeEven": "even", - - /* Empty row */ - "sRowEmpty": "dataTables_empty", - - /* Features */ - "sWrapper": "dataTables_wrapper", - "sFilter": "dataTables_filter", - "sInfo": "dataTables_info", - "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */ - "sLength": "dataTables_length", - "sProcessing": "dataTables_processing", - - /* Sorting */ - "sSortAsc": "sorting_asc", - "sSortDesc": "sorting_desc", - "sSortable": "sorting", /* Sortable in both directions */ - "sSortableAsc": "sorting_asc_disabled", - "sSortableDesc": "sorting_desc_disabled", - "sSortableNone": "sorting_disabled", - "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ - - /* Filtering */ - "sFilterInput": "", - - /* Page length */ - "sLengthSelect": "", - - /* Scrolling */ - "sScrollWrapper": "dataTables_scroll", - "sScrollHead": "dataTables_scrollHead", - "sScrollHeadInner": "dataTables_scrollHeadInner", - "sScrollBody": "dataTables_scrollBody", - "sScrollFoot": "dataTables_scrollFoot", - "sScrollFootInner": "dataTables_scrollFootInner", - - /* Misc */ - "sHeaderTH": "", - "sFooterTH": "", - - // Deprecated - "sSortJUIAsc": "", - "sSortJUIDesc": "", - "sSortJUI": "", - "sSortJUIAscAllowed": "", - "sSortJUIDescAllowed": "", - "sSortJUIWrapper": "", - "sSortIcon": "", - "sJUIHeader": "", - "sJUIFooter": "" - } ); - - - var extPagination = DataTable.ext.pager; - - function _numbers ( page, pages ) { - var - numbers = [], - buttons = extPagination.numbers_length, - half = Math.floor( buttons / 2 ), - i = 1; - - if ( pages <= buttons ) { - numbers = _range( 0, pages ); - } - else if ( page <= half ) { - numbers = _range( 0, buttons-2 ); - numbers.push( 'ellipsis' ); - numbers.push( pages-1 ); - } - else if ( page >= pages - 1 - half ) { - numbers = _range( pages-(buttons-2), pages ); - numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6 - numbers.splice( 0, 0, 0 ); - } - else { - numbers = _range( page-half+2, page+half-1 ); - numbers.push( 'ellipsis' ); - numbers.push( pages-1 ); - numbers.splice( 0, 0, 'ellipsis' ); - numbers.splice( 0, 0, 0 ); - } - - numbers.DT_el = 'span'; - return numbers; - } - - - $.extend( extPagination, { - simple: function ( page, pages ) { - return [ 'previous', 'next' ]; - }, - - full: function ( page, pages ) { - return [ 'first', 'previous', 'next', 'last' ]; - }, - - numbers: function ( page, pages ) { - return [ _numbers(page, pages) ]; - }, - - simple_numbers: function ( page, pages ) { - return [ 'previous', _numbers(page, pages), 'next' ]; - }, - - full_numbers: function ( page, pages ) { - return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ]; - }, - - first_last_numbers: function (page, pages) { - return ['first', _numbers(page, pages), 'last']; - }, - - // For testing and plug-ins to use - _numbers: _numbers, - - // Number of number buttons (including ellipsis) to show. _Must be odd!_ - numbers_length: 7 - } ); - - - $.extend( true, DataTable.ext.renderer, { - pageButton: { - _: function ( settings, host, idx, buttons, page, pages ) { - var classes = settings.oClasses; - var lang = settings.oLanguage.oPaginate; - var aria = settings.oLanguage.oAria.paginate || {}; - var btnDisplay, btnClass, counter=0; - - var attach = function( container, buttons ) { - var i, ien, node, button, tabIndex; - var disabledClass = classes.sPageButtonDisabled; - var clickHandler = function ( e ) { - _fnPageChange( settings, e.data.action, true ); - }; - - for ( i=0, ien=buttons.length ; i<ien ; i++ ) { - button = buttons[i]; - - if ( Array.isArray( button ) ) { - var inner = $( '<'+(button.DT_el || 'div')+'/>' ) - .appendTo( container ); - attach( inner, button ); - } - else { - btnDisplay = null; - btnClass = button; - tabIndex = settings.iTabIndex; - - switch ( button ) { - case 'ellipsis': - container.append('<span class="ellipsis">…</span>'); - break; - - case 'first': - btnDisplay = lang.sFirst; - - if ( page === 0 ) { - tabIndex = -1; - btnClass += ' ' + disabledClass; - } - break; - - case 'previous': - btnDisplay = lang.sPrevious; - - if ( page === 0 ) { - tabIndex = -1; - btnClass += ' ' + disabledClass; - } - break; - - case 'next': - btnDisplay = lang.sNext; - - if ( pages === 0 || page === pages-1 ) { - tabIndex = -1; - btnClass += ' ' + disabledClass; - } - break; - - case 'last': - btnDisplay = lang.sLast; - - if ( pages === 0 || page === pages-1 ) { - tabIndex = -1; - btnClass += ' ' + disabledClass; - } - break; - - default: - btnDisplay = settings.fnFormatNumber( button + 1 ); - btnClass = page === button ? - classes.sPageButtonActive : ''; - break; - } - - if ( btnDisplay !== null ) { - node = $('<a>', { - 'class': classes.sPageButton+' '+btnClass, - 'aria-controls': settings.sTableId, - 'aria-label': aria[ button ], - 'data-dt-idx': counter, - 'tabindex': tabIndex, - 'id': idx === 0 && typeof button === 'string' ? - settings.sTableId +'_'+ button : - null - } ) - .html( btnDisplay ) - .appendTo( container ); - - _fnBindAction( - node, {action: button}, clickHandler - ); - - counter++; - } - } - } - }; - - // IE9 throws an 'unknown error' if document.activeElement is used - // inside an iframe or frame. Try / catch the error. Not good for - // accessibility, but neither are frames. - var activeEl; - - try { - // Because this approach is destroying and recreating the paging - // elements, focus is lost on the select button which is bad for - // accessibility. So we want to restore focus once the draw has - // completed - activeEl = $(host).find(document.activeElement).data('dt-idx'); - } - catch (e) {} - - attach( $(host).empty(), buttons ); - - if ( activeEl !== undefined ) { - $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); - } - } - } - } ); - - - - // Built in type detection. See model.ext.aTypes for information about - // what is required from this methods. - $.extend( DataTable.ext.type.detect, [ - // Plain numbers - first since V8 detects some plain numbers as dates - // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _isNumber( d, decimal ) ? 'num'+decimal : null; - }, - - // Dates (only those recognised by the browser's Date.parse) - function ( d, settings ) - { - // V8 tries _very_ hard to make a string passed into `Date.parse()` - // valid, so we need to use a regex to restrict date formats. Use a - // plug-in for anything other than ISO8601 style strings - if ( d && !(d instanceof Date) && ! _re_date.test(d) ) { - return null; - } - var parsed = Date.parse(d); - return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; - }, - - // Formatted numbers - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; - }, - - // HTML numeric - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; - }, - - // HTML numeric, formatted - function ( d, settings ) - { - var decimal = settings.oLanguage.sDecimal; - return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; - }, - - // HTML (this is strict checking - there must be html) - function ( d, settings ) - { - return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? - 'html' : null; - } - ] ); - - - - // Filter formatting functions. See model.ext.ofnSearch for information about - // what is required from these methods. - // - // Note that additional search methods are added for the html numbers and - // html formatted numbers by `_addNumericSort()` when we know what the decimal - // place is - - - $.extend( DataTable.ext.type.search, { - html: function ( data ) { - return _empty(data) ? - data : - typeof data === 'string' ? - data - .replace( _re_new_lines, " " ) - .replace( _re_html, "" ) : - ''; - }, - - string: function ( data ) { - return _empty(data) ? - data : - typeof data === 'string' ? - data.replace( _re_new_lines, " " ) : - data; - } - } ); - - - - var __numericReplace = function ( d, decimalPlace, re1, re2 ) { - if ( d !== 0 && (!d || d === '-') ) { - return -Infinity; - } - - // If a decimal place other than `.` is used, it needs to be given to the - // function so we can detect it and replace with a `.` which is the only - // decimal place Javascript recognises - it is not locale aware. - if ( decimalPlace ) { - d = _numToDecimal( d, decimalPlace ); - } - - if ( d.replace ) { - if ( re1 ) { - d = d.replace( re1, '' ); - } - - if ( re2 ) { - d = d.replace( re2, '' ); - } - } - - return d * 1; - }; - - - // Add the numeric 'deformatting' functions for sorting and search. This is done - // in a function to provide an easy ability for the language options to add - // additional methods if a non-period decimal place is used. - function _addNumericSort ( decimalPlace ) { - $.each( - { - // Plain numbers - "num": function ( d ) { - return __numericReplace( d, decimalPlace ); - }, - - // Formatted numbers - "num-fmt": function ( d ) { - return __numericReplace( d, decimalPlace, _re_formatted_numeric ); - }, - - // HTML numeric - "html-num": function ( d ) { - return __numericReplace( d, decimalPlace, _re_html ); - }, - - // HTML numeric, formatted - "html-num-fmt": function ( d ) { - return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); - } - }, - function ( key, fn ) { - // Add the ordering method - _ext.type.order[ key+decimalPlace+'-pre' ] = fn; - - // For HTML types add a search formatter that will strip the HTML - if ( key.match(/^html\-/) ) { - _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; - } - } - ); - } - - - // Default sort methods - $.extend( _ext.type.order, { - // Dates - "date-pre": function ( d ) { - var ts = Date.parse( d ); - return isNaN(ts) ? -Infinity : ts; - }, - - // html - "html-pre": function ( a ) { - return _empty(a) ? - '' : - a.replace ? - a.replace( /<.*?>/g, "" ).toLowerCase() : - a+''; - }, - - // string - "string-pre": function ( a ) { - // This is a little complex, but faster than always calling toString, - // http://jsperf.com/tostring-v-check - return _empty(a) ? - '' : - typeof a === 'string' ? - a.toLowerCase() : - ! a.toString ? - '' : - a.toString(); - }, - - // string-asc and -desc are retained only for compatibility with the old - // sort methods - "string-asc": function ( x, y ) { - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }, - - "string-desc": function ( x, y ) { - return ((x < y) ? 1 : ((x > y) ? -1 : 0)); - } - } ); - - - // Numeric sorting types - order doesn't matter here - _addNumericSort( '' ); - - - $.extend( true, DataTable.ext.renderer, { - header: { - _: function ( settings, cell, column, classes ) { - // No additional mark-up required - // Attach a sort listener to update on sort - note that using the - // `DT` namespace will allow the event to be removed automatically - // on destroy, while the `dt` namespaced event is the one we are - // listening for - $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { - if ( settings !== ctx ) { // need to check this this is the host - return; // table, not a nested one - } - - var colIdx = column.idx; - - cell - .removeClass( - column.sSortingClass +' '+ - classes.sSortAsc +' '+ - classes.sSortDesc - ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortAsc : columns[ colIdx ] == 'desc' ? - classes.sSortDesc : - column.sSortingClass - ); - } ); - }, - - jqueryui: function ( settings, cell, column, classes ) { - $('<div/>') - .addClass( classes.sSortJUIWrapper ) - .append( cell.contents() ) - .append( $('<span/>') - .addClass( classes.sSortIcon+' '+column.sSortingClassJUI ) - ) - .appendTo( cell ); - - // Attach a sort listener to update on sort - $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { - if ( settings !== ctx ) { - return; - } - - var colIdx = column.idx; - - cell - .removeClass( classes.sSortAsc +" "+classes.sSortDesc ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortAsc : columns[ colIdx ] == 'desc' ? - classes.sSortDesc : - column.sSortingClass - ); - - cell - .find( 'span.'+classes.sSortIcon ) - .removeClass( - classes.sSortJUIAsc +" "+ - classes.sSortJUIDesc +" "+ - classes.sSortJUI +" "+ - classes.sSortJUIAscAllowed +" "+ - classes.sSortJUIDescAllowed - ) - .addClass( columns[ colIdx ] == 'asc' ? - classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ? - classes.sSortJUIDesc : - column.sSortingClassJUI - ); - } ); - } - } - } ); - - /* - * Public helper functions. These aren't used internally by DataTables, or - * called by any of the options passed into DataTables, but they can be used - * externally by developers working with DataTables. They are helper functions - * to make working with DataTables a little bit easier. - */ - - var __htmlEscapeEntities = function ( d ) { - return typeof d === 'string' ? - d - .replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') : - d; - }; - - /** - * Helpers for `columns.render`. - * - * The options defined here can be used with the `columns.render` initialisation - * option to provide a display renderer. The following functions are defined: - * - * * `number` - Will format numeric data (defined by `columns.data`) for - * display, retaining the original unformatted data for sorting and filtering. - * It takes 5 parameters: - * * `string` - Thousands grouping separator - * * `string` - Decimal point indicator - * * `integer` - Number of decimal points to show - * * `string` (optional) - Prefix. - * * `string` (optional) - Postfix (/suffix). - * * `text` - Escape HTML to help prevent XSS attacks. It has no optional - * parameters. - * - * @example - * // Column definition using the number renderer - * { - * data: "salary", - * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) - * } - * - * @namespace - */ - DataTable.render = { - number: function ( thousands, decimal, precision, prefix, postfix ) { - return { - display: function ( d ) { - if ( typeof d !== 'number' && typeof d !== 'string' ) { - return d; - } - - var negative = d < 0 ? '-' : ''; - var flo = parseFloat( d ); - - // If NaN then there isn't much formatting that we can do - just - // return immediately, escaping any HTML (this was supposed to - // be a number after all) - if ( isNaN( flo ) ) { - return __htmlEscapeEntities( d ); - } - - flo = flo.toFixed( precision ); - d = Math.abs( flo ); - - var intPart = parseInt( d, 10 ); - var floatPart = precision ? - decimal+(d - intPart).toFixed( precision ).substring( 2 ): - ''; - - return negative + (prefix||'') + - intPart.toString().replace( - /\B(?=(\d{3})+(?!\d))/g, thousands - ) + - floatPart + - (postfix||''); - } - }; - }, - - text: function () { - return { - display: __htmlEscapeEntities, - filter: __htmlEscapeEntities - }; - } - }; - - - /* - * This is really a good bit rubbish this method of exposing the internal methods - * publicly... - To be fixed in 2.0 using methods on the prototype - */ - - - /** - * Create a wrapper function for exporting an internal functions to an external API. - * @param {string} fn API function name - * @returns {function} wrapped function - * @memberof DataTable#internal - */ - function _fnExternApiFunc (fn) - { - return function() { - var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat( - Array.prototype.slice.call(arguments) - ); - return DataTable.ext.internal[fn].apply( this, args ); - }; - } - - - /** - * Reference to internal functions for use by plug-in developers. Note that - * these methods are references to internal functions and are considered to be - * private. If you use these methods, be aware that they are liable to change - * between versions. - * @namespace - */ - $.extend( DataTable.ext.internal, { - _fnExternApiFunc: _fnExternApiFunc, - _fnBuildAjax: _fnBuildAjax, - _fnAjaxUpdate: _fnAjaxUpdate, - _fnAjaxParameters: _fnAjaxParameters, - _fnAjaxUpdateDraw: _fnAjaxUpdateDraw, - _fnAjaxDataSrc: _fnAjaxDataSrc, - _fnAddColumn: _fnAddColumn, - _fnColumnOptions: _fnColumnOptions, - _fnAdjustColumnSizing: _fnAdjustColumnSizing, - _fnVisibleToColumnIndex: _fnVisibleToColumnIndex, - _fnColumnIndexToVisible: _fnColumnIndexToVisible, - _fnVisbleColumns: _fnVisbleColumns, - _fnGetColumns: _fnGetColumns, - _fnColumnTypes: _fnColumnTypes, - _fnApplyColumnDefs: _fnApplyColumnDefs, - _fnHungarianMap: _fnHungarianMap, - _fnCamelToHungarian: _fnCamelToHungarian, - _fnLanguageCompat: _fnLanguageCompat, - _fnBrowserDetect: _fnBrowserDetect, - _fnAddData: _fnAddData, - _fnAddTr: _fnAddTr, - _fnNodeToDataIndex: _fnNodeToDataIndex, - _fnNodeToColumnIndex: _fnNodeToColumnIndex, - _fnGetCellData: _fnGetCellData, - _fnSetCellData: _fnSetCellData, - _fnSplitObjNotation: _fnSplitObjNotation, - _fnGetObjectDataFn: _fnGetObjectDataFn, - _fnSetObjectDataFn: _fnSetObjectDataFn, - _fnGetDataMaster: _fnGetDataMaster, - _fnClearTable: _fnClearTable, - _fnDeleteIndex: _fnDeleteIndex, - _fnInvalidate: _fnInvalidate, - _fnGetRowElements: _fnGetRowElements, - _fnCreateTr: _fnCreateTr, - _fnBuildHead: _fnBuildHead, - _fnDrawHead: _fnDrawHead, - _fnDraw: _fnDraw, - _fnReDraw: _fnReDraw, - _fnAddOptionsHtml: _fnAddOptionsHtml, - _fnDetectHeader: _fnDetectHeader, - _fnGetUniqueThs: _fnGetUniqueThs, - _fnFeatureHtmlFilter: _fnFeatureHtmlFilter, - _fnFilterComplete: _fnFilterComplete, - _fnFilterCustom: _fnFilterCustom, - _fnFilterColumn: _fnFilterColumn, - _fnFilter: _fnFilter, - _fnFilterCreateSearch: _fnFilterCreateSearch, - _fnEscapeRegex: _fnEscapeRegex, - _fnFilterData: _fnFilterData, - _fnFeatureHtmlInfo: _fnFeatureHtmlInfo, - _fnUpdateInfo: _fnUpdateInfo, - _fnInfoMacros: _fnInfoMacros, - _fnInitialise: _fnInitialise, - _fnInitComplete: _fnInitComplete, - _fnLengthChange: _fnLengthChange, - _fnFeatureHtmlLength: _fnFeatureHtmlLength, - _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate, - _fnPageChange: _fnPageChange, - _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing, - _fnProcessingDisplay: _fnProcessingDisplay, - _fnFeatureHtmlTable: _fnFeatureHtmlTable, - _fnScrollDraw: _fnScrollDraw, - _fnApplyToChildren: _fnApplyToChildren, - _fnCalculateColumnWidths: _fnCalculateColumnWidths, - _fnThrottle: _fnThrottle, - _fnConvertToWidth: _fnConvertToWidth, - _fnGetWidestNode: _fnGetWidestNode, - _fnGetMaxLenString: _fnGetMaxLenString, - _fnStringToCss: _fnStringToCss, - _fnSortFlatten: _fnSortFlatten, - _fnSort: _fnSort, - _fnSortAria: _fnSortAria, - _fnSortListener: _fnSortListener, - _fnSortAttachListener: _fnSortAttachListener, - _fnSortingClasses: _fnSortingClasses, - _fnSortData: _fnSortData, - _fnSaveState: _fnSaveState, - _fnLoadState: _fnLoadState, - _fnSettingsFromNode: _fnSettingsFromNode, - _fnLog: _fnLog, - _fnMap: _fnMap, - _fnBindAction: _fnBindAction, - _fnCallbackReg: _fnCallbackReg, - _fnCallbackFire: _fnCallbackFire, - _fnLengthOverflow: _fnLengthOverflow, - _fnRenderer: _fnRenderer, - _fnDataSource: _fnDataSource, - _fnRowAttributes: _fnRowAttributes, - _fnExtend: _fnExtend, - _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant - // in 1.10, so this dead-end function is - // added to prevent errors - } ); - - - // jQuery access - $.fn.dataTable = DataTable; - - // Provide access to the host jQuery object (circular reference) - DataTable.$ = $; - - // Legacy aliases - $.fn.dataTableSettings = DataTable.settings; - $.fn.dataTableExt = DataTable.ext; - - // With a capital `D` we return a DataTables API instance rather than a - // jQuery object - $.fn.DataTable = function ( opts ) { - return $(this).dataTable( opts ).api(); - }; - - // All properties that are available to $.fn.dataTable should also be - // available on $.fn.DataTable - $.each( DataTable, function ( prop, val ) { - $.fn.DataTable[ prop ] = val; - } ); - - - // Information about events fired by DataTables - for documentation. - /** - * Draw event, fired whenever the table is redrawn on the page, at the same - * point as fnDrawCallback. This may be useful for binding events or - * performing calculations when the table is altered at all. - * @name DataTable#draw.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * Search event, fired when the searching applied to the table (using the - * built-in global search, or column filters) is altered. - * @name DataTable#search.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * Page change event, fired when the paging of the table is altered. - * @name DataTable#page.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * Order event, fired when the ordering applied to the table is altered. - * @name DataTable#order.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * DataTables initialisation complete event, fired when the table is fully - * drawn, including Ajax data loaded, if Ajax data is required. - * @name DataTable#init.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {object} json The JSON object request from the server - only - * present if client-side Ajax sourced data is used</li></ol> - */ - - /** - * State save event, fired when the table has changed state a new state save - * is required. This event allows modification of the state saving object - * prior to actually doing the save, including addition or other state - * properties (for plug-ins) or modification of a DataTables core property. - * @name DataTable#stateSaveParams.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {object} json The state information to be saved - */ - - /** - * State load event, fired when the table is loading state from the stored - * data, but prior to the settings object being modified by the saved state - * - allowing modification of the saved state is required or loading of - * state for a plug-in. - * @name DataTable#stateLoadParams.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {object} json The saved state information - */ - - /** - * State loaded event, fired when state has been loaded from stored data and - * the settings object has been modified by the loaded data. - * @name DataTable#stateLoaded.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {object} json The saved state information - */ - - /** - * Processing event, fired when DataTables is doing some kind of processing - * (be it, order, search or anything else). It can be used to indicate to - * the end user that there is something happening, or that something has - * finished. - * @name DataTable#processing.dt - * @event - * @param {event} e jQuery event object - * @param {object} oSettings DataTables settings object - * @param {boolean} bShow Flag for if DataTables is doing processing or not - */ - - /** - * Ajax (XHR) event, fired whenever an Ajax request is completed from a - * request to made to the server for new data. This event is called before - * DataTables processed the returned data, so it can also be used to pre- - * process the data returned from the server, if needed. - * - * Note that this trigger is called in `fnServerData`, if you override - * `fnServerData` and which to use this event, you need to trigger it in you - * success function. - * @name DataTable#xhr.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - * @param {object} json JSON returned from the server - * - * @example - * // Use a custom property returned from the server in another DOM element - * $('#table').dataTable().on('xhr.dt', function (e, settings, json) { - * $('#status').html( json.status ); - * } ); - * - * @example - * // Pre-process the data returned from the server - * $('#table').dataTable().on('xhr.dt', function (e, settings, json) { - * for ( var i=0, ien=json.aaData.length ; i<ien ; i++ ) { - * json.aaData[i].sum = json.aaData[i].one + json.aaData[i].two; - * } - * // Note no return - manipulate the data directly in the JSON object. - * } ); - */ - - /** - * Destroy event, fired when the DataTable is destroyed by calling fnDestroy - * or passing the bDestroy:true parameter in the initialisation object. This - * can be used to remove bound events, added DOM nodes, etc. - * @name DataTable#destroy.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * Page length change event, fired when number of records to show on each - * page (the length) is changed. - * @name DataTable#length.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - * @param {integer} len New length - */ - - /** - * Column sizing has changed. - * @name DataTable#column-sizing.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - */ - - /** - * Column visibility has changed. - * @name DataTable#column-visibility.dt - * @event - * @param {event} e jQuery event object - * @param {object} o DataTables settings object {@link DataTable.models.oSettings} - * @param {int} column Column index - * @param {bool} vis `false` if column now hidden, or `true` if visible - */ - - return $.fn.dataTable; -})); diff --git a/public/plugins/datatables/jquery.dataTables.min.css b/public/plugins/datatables/jquery.dataTables.min.css new file mode 100644 index 000000000..aff1ca672 --- /dev/null +++ b/public/plugins/datatables/jquery.dataTables.min.css @@ -0,0 +1 @@ +:root{--dt-row-selected: 13, 110, 253;--dt-row-selected-text: 255, 255, 255;--dt-row-selected-link: 9, 10, 11}table.dataTable td.dt-control{text-align:center;cursor:pointer}table.dataTable td.dt-control:before{height:1em;width:1em;margin-top:-9px;display:inline-block;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable tr.dt-hasChild td.dt-control:before{content:"-";background-color:#d33333}table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting_asc_disabled,table.dataTable thead>tr>th.sorting_desc_disabled,table.dataTable thead>tr>td.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting_asc_disabled,table.dataTable thead>tr>td.sorting_desc_disabled{cursor:pointer;position:relative;padding-right:26px}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after{position:absolute;display:block;opacity:.125;right:10px;line-height:9px;font-size:.8em}table.dataTable thead>tr>th.sorting:before,table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:before,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>th.sorting_desc_disabled:before,table.dataTable thead>tr>td.sorting:before,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:before,table.dataTable thead>tr>td.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:before{bottom:50%;content:"▲";content:"▲"/""}table.dataTable thead>tr>th.sorting:after,table.dataTable thead>tr>th.sorting_asc:after,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>th.sorting_asc_disabled:after,table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting:after,table.dataTable thead>tr>td.sorting_asc:after,table.dataTable thead>tr>td.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc_disabled:after,table.dataTable thead>tr>td.sorting_desc_disabled:after{top:50%;content:"▼";content:"▼"/""}table.dataTable thead>tr>th.sorting_asc:before,table.dataTable thead>tr>th.sorting_desc:after,table.dataTable thead>tr>td.sorting_asc:before,table.dataTable thead>tr>td.sorting_desc:after{opacity:.6}table.dataTable thead>tr>th.sorting_desc_disabled:after,table.dataTable thead>tr>th.sorting_asc_disabled:before,table.dataTable thead>tr>td.sorting_desc_disabled:after,table.dataTable thead>tr>td.sorting_asc_disabled:before{display:none}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}div.dataTables_scrollBody>table.dataTable>thead>tr>th:before,div.dataTables_scrollBody>table.dataTable>thead>tr>th:after,div.dataTables_scrollBody>table.dataTable>thead>tr>td:before,div.dataTables_scrollBody>table.dataTable>thead>tr>td:after{display:none}div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:2px}div.dataTables_processing>div:last-child{position:relative;width:80px;height:15px;margin:1em auto}div.dataTables_processing>div:last-child>div{position:absolute;top:0;width:13px;height:13px;border-radius:50%;background:rgb(13, 110, 253);background:rgb(var(--dt-row-selected));animation-timing-function:cubic-bezier(0, 1, 1, 0)}div.dataTables_processing>div:last-child>div:nth-child(1){left:8px;animation:datatables-loader-1 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(2){left:8px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(3){left:32px;animation:datatables-loader-2 .6s infinite}div.dataTables_processing>div:last-child>div:nth-child(4){left:56px;animation:datatables-loader-3 .6s infinite}@keyframes datatables-loader-1{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes datatables-loader-3{0%{transform:scale(1)}100%{transform:scale(0)}}@keyframes datatables-loader-2{0%{transform:translate(0, 0)}100%{transform:translate(24px, 0)}}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th,table.dataTable thead td,table.dataTable tfoot th,table.dataTable tfoot td{text-align:left}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px;border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 10px 6px 10px;border-top:1px solid rgba(0, 0, 0, 0.3)}table.dataTable tbody tr{background-color:transparent}table.dataTable tbody tr.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.9);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9);color:rgb(255, 255, 255);color:rgb(var(--dt-row-selected-text))}table.dataTable tbody tr.selected a{color:rgb(9, 10, 11);color:rgb(var(--dt-row-selected-link))}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid rgba(0, 0, 0, 0.15);border-right:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid rgba(0, 0, 0, 0.15)}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe>tbody>tr.odd>*,table.dataTable.display>tbody>tr.odd>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.023)}table.dataTable.stripe>tbody>tr.odd.selected>*,table.dataTable.display>tbody>tr.odd.selected>*{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.923);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.923))}table.dataTable.hover>tbody>tr:hover>*,table.dataTable.display>tbody>tr:hover>*{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.035)}table.dataTable.hover>tbody>tr.selected:hover>*,table.dataTable.display>tbody>tr.selected:hover>*{box-shadow:inset 0 0 0 9999px #0d6efd !important;box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 1)) !important}table.dataTable.order-column>tbody tr>.sorting_1,table.dataTable.order-column>tbody tr>.sorting_2,table.dataTable.order-column>tbody tr>.sorting_3,table.dataTable.display>tbody tr>.sorting_1,table.dataTable.display>tbody tr>.sorting_2,table.dataTable.display>tbody tr>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019)}table.dataTable.order-column>tbody tr.selected>.sorting_1,table.dataTable.order-column>tbody tr.selected>.sorting_2,table.dataTable.order-column>tbody tr.selected>.sorting_3,table.dataTable.display>tbody tr.selected>.sorting_1,table.dataTable.display>tbody tr.selected>.sorting_2,table.dataTable.display>tbody tr.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.919))}table.dataTable.display>tbody>tr.odd>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.054)}table.dataTable.display>tbody>tr.odd>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.047)}table.dataTable.display>tbody>tr.odd>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.odd>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.039)}table.dataTable.display>tbody>tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.954);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.954))}table.dataTable.display>tbody>tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.947);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.947))}table.dataTable.display>tbody>tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.odd.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.939);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.939))}table.dataTable.display>tbody>tr.even>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.019)}table.dataTable.display>tbody>tr.even>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.011)}table.dataTable.display>tbody>tr.even>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.003)}table.dataTable.display>tbody>tr.even.selected>.sorting_1,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.919);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.919))}table.dataTable.display>tbody>tr.even.selected>.sorting_2,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.911);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.911))}table.dataTable.display>tbody>tr.even.selected>.sorting_3,table.dataTable.order-column.stripe>tbody>tr.even.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.903);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.903))}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.082)}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.074)}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(0, 0, 0, 0.062)}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.982);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.982))}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.974);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.974))}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{box-shadow:inset 0 0 0 9999px rgba(13, 110, 253, 0.962);box-shadow:inset 0 0 0 9999px rgba(var(--dt-row-selected, 0.962))}table.dataTable.no-footer{border-bottom:1px solid rgba(0, 0, 0, 0.3)}table.dataTable.compact thead th,table.dataTable.compact thead td,table.dataTable.compact tfoot th,table.dataTable.compact tfoot td,table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;color:inherit !important;border:1px solid transparent;border-radius:2px;background:transparent}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:inherit !important;border:1px solid rgba(0, 0, 0, 0.3);background-color:rgba(230, 230, 230, 0.1);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.1)), color-stop(100%, rgba(0, 0, 0, 0.1)));background:-webkit-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-moz-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-ms-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:-o-linear-gradient(top, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%);background:linear-gradient(to bottom, rgba(230, 230, 230, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:inherit}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid rgba(0, 0, 0, 0.3)}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} diff --git a/public/plugins/datatables/jquery.dataTables.min.js b/public/plugins/datatables/jquery.dataTables.min.js index 142c04f2e..c89263c6d 100644 --- a/public/plugins/datatables/jquery.dataTables.min.js +++ b/public/plugins/datatables/jquery.dataTables.min.js @@ -1,168 +1,4 @@ -/*! - DataTables 1.10.23 - ©2008-2020 SpryMedia Ltd - datatables.net/license -*/ -(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(E){return h(E,window,document)}):"object"===typeof exports?module.exports=function(E,H){E||(E=window);H||(H="undefined"!==typeof window?require("jquery"):require("jquery")(E));return h(H,E,E.document)}:h(jQuery,window,document)})(function(h,E,H,k){function $(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()), -d[c]=e,"o"===b[1]&&$(a[e])});a._hungarianMap=d}function J(a,b,c){a._hungarianMap||$(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),J(a[d],b[d],c)):b[d]=b[e]})}function Ea(a){var b=l.defaults.oLanguage,c=b.sDecimal;c&&Fa(c);if(a){var d=a.sZeroRecords;!a.sEmptyTable&&(d&&"No data available in table"===b.sEmptyTable)&&F(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(d&&"Loading..."===b.sLoadingRecords)&&F(a, -a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&c!==a&&Fa(a)}}function gb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%": -"");"boolean"===typeof a.scrollX&&(a.scrollX=a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&J(l.models.oSearch,a[b])}function hb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;"number"===typeof b&&!Array.isArray(b)&&(a.aDataSort=[b])}function ib(a){if(!l.__browser){var b={};l.__browser=b;var c=h("<div/>").css({position:"fixed",top:0,left:-1*h(E).scrollLeft(),height:1, -width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,l.__browser);a.oScroll.iBarWidth=l.__browser.barWidth} -function jb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==e;)a.hasOwnProperty(d)&&(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ga(a,b){var c=l.defaults.column,d=a.aoColumns.length,c=h.extend({},l.models.oColumn,c,{nTh:b?b:H.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},l.models.oSearch,c[d]);la(a,d,h(b).data())}function la(a,b,c){var b=a.aoColumns[b], -d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(hb(c),J(l.defaults.column,c,!0),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),h.extend(b,c),F(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),F(b,c,"aDataSort"));var g=b.mData,j=S(g),i= -b.mRender?S(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return N(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass= -d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function aa(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ha(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&ma(a);u(a,null,"column-sizing",[a])}function ba(a,b){var c=na(a,"bVisible"); -return"number"===typeof c[b]?c[b]:null}function ca(a,b){var c=na(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function W(a){var b=0;h.each(a.aoColumns,function(a,d){d.bVisible&&"none"!==h(d.nTh).css("display")&&b++});return b}function na(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ia(a){var b=a.aoColumns,c=a.aoData,d=l.ext.type.detect,e,f,g,j,i,h,m,q,r;e=0;for(f=b.length;e<f;e++)if(m=b[e],r=[],!m.sType&&m._sManualType)m.sType=m._sManualType;else if(!m.sType){g= -0;for(j=d.length;g<j;g++){i=0;for(h=c.length;i<h;i++){r[i]===k&&(r[i]=B(a,i,e,"type"));q=d[g](r[i],a);if(!q&&g!==d.length-1)break;if("html"===q)break}if(q){m.sType=q;break}}m.sType||(m.sType="string")}}function kb(a,b,c,d){var e,f,g,j,i,n,m=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var q=n.targets!==k?n.targets:n.aTargets;Array.isArray(q)||(q=[q]);f=0;for(g=q.length;f<g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;m.length<=q[f];)Ga(a);d(q[f],n)}else if("number"===typeof q[f]&&0>q[f])d(m.length+ -q[f],n);else if("string"===typeof q[f]){j=0;for(i=m.length;j<i;j++)("_all"==q[f]||h(m[j].nTh).hasClass(q[f]))&&d(j,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function O(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},l.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,j=0,i=g.length;j<i;j++)g[j].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ja(a,e,c,d);return e}function oa(a,b){var c;b instanceof -h||(b=h(b));return b.map(function(b,e){c=Ka(a,e);return O(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,i=f.fnGetData(g,d,{settings:a,row:b,col:c});if(i===k)return a.iDrawError!=e&&null===j&&(K(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=e),j;if((i===g||null===i)&&null!==j&&d!==k)i=j;else if("function"===typeof i)return i.call(g);return null=== -i&&"display"==d?"":i}function lb(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d,{settings:a,row:b,col:c})}function La(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\\./g,".")})}function S(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=S(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&& -(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,j;if(""!==f){j=La(f);for(var i=0,h=j.length;i<h;i++){f=j[i].match(da);g=j[i].match(X);if(f){j[i]=j[i].replace(da,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");if(Array.isArray(a)){i=0;for(h=a.length;i<h;i++)g.push(c(a[i],b,j))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(X,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===k)return k;a=a[j[i]]}}return a}; -return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function N(a){if(h.isPlainObject(a))return N(a._);if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=La(e),f;f=e[e.length-1];for(var g,j,i=0,h=e.length-1;i<h;i++){if("__proto__"===e[i]||"constructor"===e[i])throw Error("Cannot set prototype values");g=e[i].match(da);j= -e[i].match(X);if(g){e[i]=e[i].replace(da,"");a[e[i]]=[];f=e.slice();f.splice(0,i+1);g=f.join(".");if(Array.isArray(d)){j=0;for(h=d.length;j<h;j++)f={},b(f,d[j],g),a[e[i]].push(f)}else a[e[i]]=d;return}j&&(e[i]=e[i].replace(X,""),a=a[e[i]](d));if(null===a[e[i]]||a[e[i]]===k)a[e[i]]={};a=a[e[i]]}if(f.match(X))a[f.replace(X,"")](d);else a[f.replace(da,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ma(a){return C(a.aoData,"_aData")}function pa(a){a.aoData.length=0; -a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function qa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ea(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ka(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}e._aSortData=null; -e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;Na(a,e)}}function Ka(a,b,c,d){var e=[],f=b.firstChild,g,j,i=0,h,m=a.aoColumns,q=a._rowReadObject,d=d!==k?d:q?{}:[],r=function(a,b){if("string"===typeof a){var c=a.indexOf("@");-1!==c&&(c=a.substring(c+1),N(a)(d,b.getAttribute(c)))}},G=function(a){if(c===k||c===i)j=m[i],h=a.innerHTML.trim(),j&&j._bAttrSrc?(N(j.mData._)(d,h),r(j.mData.sort,a),r(j.mData.type,a),r(j.mData.filter,a)):q?(j._setter|| -(j._setter=N(j.mData)),j._setter(d,h)):d[i]=h;i++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)G(f),e.push(f);f=f.nextSibling}else{e=b.anCells;f=0;for(g=e.length;f<g;f++)G(e[f])}if(b=b.firstChild?b:b.nTr)(b=b.getAttribute("id"))&&N(a.rowId)(d,b);return{data:d,cells:e}}function Ja(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],j,i,n,m,q;if(null===e.nTr){j=c||H.createElement("tr");e.nTr=j;e.anCells=g;j._DT_RowIndex=b;Na(a,e);n=0;for(m=a.aoColumns.length;n<m;n++){i=a.aoColumns[n];e=(q=c? -!1:!0)?H.createElement(i.sCellType):d[n];e._DT_CellIndex={row:b,column:n};g.push(e);if(q||(i.mRender||i.mData!==n)&&(!h.isPlainObject(i.mData)||i.mData._!==n+".display"))e.innerHTML=B(a,b,n,"display");i.sClass&&(e.className+=" "+i.sClass);i.bVisible&&!c?j.appendChild(e):!i.bVisible&&c&&e.parentNode.removeChild(e);i.fnCreatedCell&&i.fnCreatedCell.call(a.oInstance,e,B(a,b,n),f,b,n)}u(a,"aoRowCreatedCallback",null,[j,f,b,g])}}function Na(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id= -e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?ra(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function mb(a){var b,c,d,e,f,g=a.nTHead,j=a.nTFoot,i=0===h("th, td",g).length,n=a.oClasses,m=a.aoColumns;i&&(e=h("<tr/>").appendTo(g));b=0;for(c=m.length;b<c;b++)f=m[b],d=h(f.nTh).addClass(f.sClass),i&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&& -(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Pa(a,"header")(a,d,f,n);i&&fa(a.aoHeader,g);h(g).children("tr").attr("role","row");h(g).children("tr").children("th, td").addClass(n.sHeaderTH);h(j).children("tr").children("th, td").addClass(n.sFooterTH);if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=m[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ga(a,b,c){var d,e,f,g=[],j=[],i=a.aoColumns.length, -n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);j.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=i=1,j[d][f]===k){a.appendChild(g[d][f].cell);for(j[d][f]=1;g[d+i]!==k&&g[d][f].cell==g[d+i][f].cell;)j[d+i][f]=1,i++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<i;c++)j[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan", -i).attr("colspan",n)}}}}function P(a){var b=u(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))D(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==y(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,D(a,!1);else if(j){if(!a.bDestroying&&!nb(a))return}else a.iDraw++;if(0!==i.length){f= -j?a.aoData.length:n;for(j=j?0:g;j<f;j++){var m=i[j],q=a.aoData[m];null===q.nTr&&Ja(a,m);var r=q.nTr;if(0!==e){var G=d[c%e];q._sRowStripe!=G&&(h(r).removeClass(q._sRowStripe).addClass(G),q._sRowStripe=G)}u(a,"aoRowCallback",null,[r,q._aData,c,j,m]);b.push(r);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:W(a),"class":a.oClasses.sRowEmpty}).html(c))[0]; -u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ma(a),g,n,i]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ma(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&ob(a);d?ha(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;P(a);a._drawHold=!1}function pb(a){var b=a.oClasses, -c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,m,q,k=0;k<f.length;k++){g=null;j=f[k];if("<"==j){i=h("<div/>")[0];n=f[k+1];if("'"==n||'"'==n){m="";for(q=2;f[k+q]!=n;)m+=f[k+q],q++;"H"==m?m=b.sJUIHeader:"F"==m&&(m=b.sJUIFooter);-1!=m.indexOf(".")?(n=m.split("."),i.id=n[0].substr(1,n[0].length- -1),i.className=n[1]):"#"==m.charAt(0)?i.id=m.substr(1,m.length-1):i.className=m;k+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=qb(a);else if("f"==j&&d.bFilter)g=rb(a);else if("r"==j&&d.bProcessing)g=sb(a);else if("t"==j)g=tb(a);else if("i"==j&&d.bInfo)g=ub(a);else if("p"==j&&d.bPaginate)g=vb(a);else if(0!==l.ext.feature.length){i=l.ext.feature;q=0;for(n=i.length;q<n;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]= -[]),i[j].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function fa(a,b){var c=h(b).children("tr"),d,e,f,g,j,i,n,m,q,k;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){m=1*e.getAttribute("colspan");q=1*e.getAttribute("rowspan");m=!m||0===m||1===m?1:m;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;n=g;k=1===m?!0:!1;for(j=0;j<m;j++)for(g=0;g<q;g++)a[f+g][n+j]= -{cell:e,unique:k},a[f+g].nTr=d}e=e.nextSibling}}}function sa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],fa(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ta(a,b,c){u(a,"aoServerParams","serverParams",[b]);if(b&&Array.isArray(b)){var d={},e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,j=a.oInstance, -i=function(b){u(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n="function"===typeof f?f(b,a):f,b="function"===typeof f&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&K(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=u(a,null,"xhr",[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==c?K(a,0,"Invalid JSON response",1):4===b.readyState&&K(a,0,"Ajax error",7));D(a,!1)}};a.oAjaxData= -b;u(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):"function"===typeof g?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function nb(a){return a.bAjaxDataGet?(a.iDraw++,D(a,!0),ta(a,wb(a),function(b){xb(a,b)}),!1):!0}function wb(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,j= -[],i,n,m,k=Y(a);g=a._iDisplayStart;i=!1!==d.bPaginate?a._iDisplayLength:-1;var r=function(a,b){j.push({name:a,value:b})};r("sEcho",a.iDraw);r("iColumns",c);r("sColumns",C(b,"sName").join(","));r("iDisplayStart",g);r("iDisplayLength",i);var G={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],m=f[g],i="function"==typeof n.mData?"function":n.mData,G.columns.push({data:i,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:m.sSearch, -regex:m.bRegex}}),r("mDataProp_"+g,i),d.bFilter&&(r("sSearch_"+g,m.sSearch),r("bRegex_"+g,m.bRegex),r("bSearchable_"+g,n.bSearchable)),d.bSort&&r("bSortable_"+g,n.bSortable);d.bFilter&&(r("sSearch",e.sSearch),r("bRegex",e.bRegex));d.bSort&&(h.each(k,function(a,b){G.order.push({column:b.col,dir:b.dir});r("iSortCol_"+a,b.col);r("sSortDir_"+a,b.dir)}),r("iSortingCols",k.length));b=l.ext.legacy.ajax;return null===b?a.sAjaxSource?j:G:b?j:G}function xb(a,b){var c=ua(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e= -b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d!==k){if(1*d<a.iDraw)return;a.iDraw=1*d}pa(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)O(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;P(a);a._bInitComplete||va(a,b);a.bAjaxDataGet=!0;D(a,!1)}function ua(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c? -b.aaData||b[c]:""!==c?S(c)(b):b}function rb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),i=function(){var b=!this.value?"":this.value;b!=e.sSearch&&(ha(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,P(a))}, -f=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,n=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).on("keyup.DT search.DT input.DT paste.DT cut.DT",f?Qa(i,f):i).on("mouseup",function(){setTimeout(function(){i.call(n[0])},10)}).on("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{n[0]!==H.activeElement&&n.val(e.sSearch)}catch(d){}});return b[0]}function ha(a,b,c){var d=a.oPreviousSearch, -e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ia(a);if("ssp"!=y(a)){yb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)zb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);Ab(a)}else f(b);a.bFiltered=!0;u(a,null,"search",[a])}function Ab(a){for(var b=l.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f< -g;f++){for(var j=[],i=0,n=c.length;i<n;i++)e=c[i],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,i)&&j.push(e);c.length=0;h.merge(c,j)}}function zb(a,b,c,d,e,f){if(""!==b){for(var g=[],j=a.aiDisplay,d=Ra(b,d,e,f),e=0;e<j.length;e++)b=a.aoData[j[e]]._aFilterData[c],d.test(b)&&g.push(j[e]);a.aiDisplay=g}}function yb(a,b,c,d,e,f){var e=Ra(b,d,e,f),g=a.oPreviousSearch.sSearch,j=a.aiDisplayMaster,i,f=[];0!==l.ext.search.length&&(c=!0);i=Bb(a);if(0>=b.length)a.aiDisplay=j.slice();else{if(i||c||d||g.length> -b.length||0!==b.indexOf(g)||a.bSorted)a.aiDisplay=j.slice();b=a.aiDisplay;for(c=0;c<b.length;c++)e.test(a.aoData[b[c]]._sFilterRow)&&f.push(b[c]);a.aiDisplay=f}}function Ra(a,b,c,d){a=b?a:Sa(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function Bb(a){var b=a.aoColumns,c,d,e,f,g,j,i,h,m=l.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<f;d++)if(h= -a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(i=B(a,d,e,"filter"),m[c.sType]&&(i=m[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(wa.innerHTML=i,i=Zb?wa.textContent:wa.innerText),i.replace&&(i=i.replace(/[\r\n\u2028]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join(" ");c=!0}return c}function Cb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}} -function Db(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function ub(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Eb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Eb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(), -g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Fb(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Fb(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/ -e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ia(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){pb(a);mb(a);ga(a,a.aoHeader);ga(a,a.aoFooter);D(a,!0);c.bAutoWidth&&Ha(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=w(f.sWidth));u(a,null,"preInit",[a]);T(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ta(a,[],function(c){var f=ua(a,c);for(b=0;b<f.length;b++)O(a,f[b]);a.iInitDisplayStart=d;T(a);D(a,!1);va(a,c)},a):(D(a,!1), -va(a))}else setTimeout(function(){ia(a)},200)}function va(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&aa(a);u(a,null,"plugin-init",[a,b]);u(a,"aoInitComplete","init",[a,b])}function Ta(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Ua(a);u(a,null,"length",[a,c])}function qb(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=Array.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)e[0][g]=new Option("number"=== -typeof d[g]?a.fnFormatNumber(d[g]):d[g],f[g]);var i=h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).on("change.DT",function(){Ta(a,h(this).val());P(a)});h(a.nTable).on("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function vb(a){var b=a.sPaginationType,c=l.ext.pager[b],d="function"===typeof c,e=function(a){P(a)},b=h("<div/>").addClass(a.oClasses.sPaging+ -b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),m=-1===i,b=m?0:Math.ceil(b/i),i=m?1:Math.ceil(h/i),h=c(b,i),k,m=0;for(k=f.p.length;m<k;m++)Pa(a,"pageButton")(a,f.p[m],m,h,b,i)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Va(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&& -(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:K(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(u(a,null,"page",[a]),c&&P(a));return b}function sb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function D(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none"); -u(a,null,"processing",[a,b])}function tb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),m=b.children("tfoot");m.length||(m=null);i=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:w(d):"100%"}).append(h("<div/>", -{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:w(d)}).append(b));m&&i.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:w(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left", -0).append("bottom"===j?g:null).append(b.children("tfoot")))));var b=i.children(),k=b[0],f=b[1],r=m?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;m&&(r.scrollLeft=a)});h(f).css("max-height",e);c.bCollapse||h(f).css("height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=r;a.aoDrawCallback.push({fn:ma,sName:"scrolling"});return i[0]}function ma(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style, -n=j.children("table"),j=a.nScrollBody,m=h(j),q=j.style,r=h(a.nScrollFoot).children("div"),l=r.children("table"),o=h(a.nTHead),p=h(a.nTable),s=p[0],u=s.style,t=a.nTFoot?h(a.nTFoot):null,U=a.oBrowser,V=U.bScrollOversize,$b=C(a.aoColumns,"nTh"),Q,L,R,xa,v=[],x=[],y=[],z=[],A,B=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};L=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==L&&a.scrollBarVis!==k)a.scrollBarVis=L,aa(a);else{a.scrollBarVis= -L;p.children("thead, tfoot").remove();t&&(R=t.clone().prependTo(p),Q=t.find("tr"),R=R.find("tr"));xa=o.clone().prependTo(p);o=o.find("tr");L=xa.find("tr");xa.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(sa(a,xa),function(b,c){A=ba(a,b);c.style.width=a.aoColumns[A].sWidth});t&&I(function(a){a.style.width=""},R);f=p.outerWidth();if(""===c){u.width="100%";if(V&&(p.find("tbody").height()>j.offsetHeight||"scroll"==m.css("overflow-y")))u.width=w(p.outerWidth()- -b);f=p.outerWidth()}else""!==d&&(u.width=w(d),f=p.outerWidth());I(B,L);I(function(a){y.push(a.innerHTML);v.push(w(h(a).css("width")))},L);I(function(a,b){if(h.inArray(a,$b)!==-1)a.style.width=v[b]},o);h(L).height(0);t&&(I(B,R),I(function(a){z.push(a.innerHTML);x.push(w(h(a).css("width")))},R),I(function(a,b){a.style.width=x[b]},Q),h(R).height(0));I(function(a,b){a.innerHTML='<div class="dataTables_sizing">'+y[b]+"</div>";a.childNodes[0].style.height="0";a.childNodes[0].style.overflow="hidden";a.style.width= -v[b]},L);t&&I(function(a,b){a.innerHTML='<div class="dataTables_sizing">'+z[b]+"</div>";a.childNodes[0].style.height="0";a.childNodes[0].style.overflow="hidden";a.style.width=x[b]},R);if(p.outerWidth()<f){Q=j.scrollHeight>j.offsetHeight||"scroll"==m.css("overflow-y")?f+b:f;if(V&&(j.scrollHeight>j.offsetHeight||"scroll"==m.css("overflow-y")))u.width=w(Q-b);(""===c||""!==d)&&K(a,1,"Possible column misalignment",6)}else Q="100%";q.width=w(Q);g.width=w(Q);t&&(a.nScrollFoot.style.width=w(Q));!e&&V&&(q.height= -w(s.offsetHeight+b));c=p.outerWidth();n[0].style.width=w(c);i.width=w(c);d=p.height()>j.clientHeight||"scroll"==m.css("overflow-y");e="padding"+(U.bScrollbarLeft?"Left":"Right");i[e]=d?b+"px":"0px";t&&(l[0].style.width=w(c),r[0].style.width=w(c),r[0].style[e]=d?b+"px":"0px");p.children("colgroup").insertBefore(p.children("thead"));m.trigger("scroll");if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function I(a,b,c){for(var d=0,e=0,f=b.length,g,j;e<f;){g=b[e].firstChild;for(j=c?c[e].firstChild: -null;g;)1===g.nodeType&&(c?a(g,j,d):a(g,d),d++),g=g.nextSibling,j=c?j.nextSibling:null;e++}}function Ha(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,f=d.sX,g=d.sXInner,j=c.length,i=na(a,"bVisible"),n=h("th",a.nTHead),m=b.getAttribute("width"),k=b.parentNode,r=!1,l,o,p=a.oBrowser,d=p.bScrollOversize;(l=b.style.width)&&-1!==l.indexOf("%")&&(m=l);for(l=0;l<i.length;l++)o=c[i[l]],null!==o.sWidth&&(o.sWidth=Gb(o.sWidthOrig,k),r=!0);if(d||!r&&!f&&!e&&j==W(a)&&j==n.length)for(l=0;l<j;l++)i=ba(a,l), -null!==i&&(c[i].sWidth=w(n.eq(l).width()));else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var s=h("<tr/>").appendTo(j.find("tbody"));j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=sa(a,j.find("thead")[0]);for(l=0;l<i.length;l++)o=c[i[l]],n[l].style.width=null!==o.sWidthOrig&&""!==o.sWidthOrig?w(o.sWidthOrig):"",o.sWidthOrig&&f&&h(n[l]).append(h("<div/>").css({width:o.sWidthOrig, -margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(l=0;l<i.length;l++)r=i[l],o=c[r],h(Hb(a,r)).clone(!1).append(o.sContentPadding).appendTo(s);h("[name]",j).removeAttr("name");o=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()<k.clientWidth&&m&&j.width(k.clientWidth)):e?j.width(k.clientWidth):m&&j.width(m);for(l=e=0;l<i.length;l++)k=h(n[l]),g=k.outerWidth()- -k.width(),k=p.bBounding?Math.ceil(n[l].getBoundingClientRect().width):k.outerWidth(),e+=k,c[i[l]].sWidth=w(k-g);b.style.width=w(e);o.remove()}m&&(b.style.width=w(m));if((m||f)&&!a._reszEvt)b=function(){h(E).on("resize.DT-"+a.sInstance,Qa(function(){aa(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function Gb(a,b){if(!a)return 0;var c=h("<div/>").css("width",w(a)).appendTo(b||H.body),d=c[0].offsetWidth;c.remove();return d}function Hb(a,b){var c=Ib(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr? -h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Ib(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace(ac,""),c=c.replace(/ /g," "),c.length>d&&(d=c.length,e=f);return e}function w(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Y(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!Array.isArray(a[0])?n.push(a):h.merge(n,a)};Array.isArray(b)&& -f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){i=n[a][0];f=e[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=e[g].sType||"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:i,col:g,dir:n[a][1],index:n[a]._idx,type:j,formatter:l.ext.type.order[j+"-pre"]})}return d}function ob(a){var b,c,d=[],e=l.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;Ia(a);h=Y(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Jb(a,j.col);if("ssp"!= -y(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)d[i[b]]=b;g===h.length?i.sort(function(a,b){var c,e,g,j,i=h.length,k=f[a]._aSortData,l=f[b]._aSortData;for(g=0;g<i;g++)if(j=h[g],c=k[j.col],e=l[j.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,l=f[a]._aSortData,o=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=l[i.col],g=o[i.col],i=e[i.type+"-"+i.dir]||e["string-"+i.dir],c=i(c,g),0!==c)return c;c=d[a];g=d[b];return c< -g?-1:c>g?1:0})}a.bSorted=!0}function Kb(a){for(var b,c,d=a.aoColumns,e=Y(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(i.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=j[e[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",b)}}function Wa(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting, -g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,C(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);T(a);"function"==typeof d&&d(a)}function Oa(a,b,c,d){var e= -a.aoColumns[c];Xa(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(D(a,!0),setTimeout(function(){Wa(a,c,b.shiftKey,d);"ssp"!==y(a)&&D(a,!1)},0)):Wa(a,c,b.shiftKey,d))})}function ya(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=Y(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(C(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;for(f=d.length;e<f;e++)g=d[e].src,h(C(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Jb(a, -b){var c=a.aoColumns[b],d=l.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ca(a,b)));for(var f,g=l.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[j]:B(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function za(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Cb(a.oPreviousSearch),columns:h.map(a.aoColumns, -function(b,d){return{visible:b.bVisible,search:Cb(a.aoPreSearchCols[d])}})};u(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Lb(a,b,c){var d,e,f=a.aoColumns,b=function(b){if(b&&b.time){var g=u(a,"aoStateLoadParams","stateLoadParams",[a,b]);if(-1===h.inArray(!1,g)&&(g=a.iStateDuration,!(0<g&&b.time<+new Date-1E3*g)&&!(b.columns&&f.length!==b.columns.length))){a.oLoadedState=h.extend(!0,{},b);b.start!==k&&(a._iDisplayStart=b.start, -a.iInitDisplayStart=b.start);b.length!==k&&(a._iDisplayLength=b.length);b.order!==k&&(a.aaSorting=[],h.each(b.order,function(b,c){a.aaSorting.push(c[0]>=f.length?[0,c[1]]:c)}));b.search!==k&&h.extend(a.oPreviousSearch,Db(b.search));if(b.columns){d=0;for(e=b.columns.length;d<e;d++)g=b.columns[d],g.visible!==k&&(f[d].bVisible=g.visible),g.search!==k&&h.extend(a.aoPreSearchCols[d],Db(g.search))}u(a,"aoStateLoaded","stateLoaded",[a,b])}}c()};if(a.oFeatures.bStateSave){var g=a.fnStateLoadCallback.call(a.oInstance, -a,b);g!==k&&b(g)}else c()}function Aa(a){var b=l.settings,a=h.inArray(a,C(b,"nTable"));return-1!==a?b[a]:null}function K(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)E.console&&console.log&&console.log(c);else if(b=l.ext,b=b.sErrMode||b.errMode,a&&u(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,d,c)}}function F(a,b,c,d){Array.isArray(c)? -h.each(c,function(c,d){Array.isArray(d)?F(a,b,d[0],d[1]):F(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Ya(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&Array.isArray(d)?d.slice():d);return a}function Xa(a,b,c){h(a).on("click.DT",b,function(b){h(a).trigger("blur");c(b)}).on("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).on("selectstart.DT",function(){return!1})} -function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function u(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Ua(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,d=l.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]|| -d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ja(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=Z(0,b):a<=d?(c=Z(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=Z(b-(c-2),b):(c=Z(a-d+2,a+d-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function Fa(a){h.each({num:function(b){return Ba(b,a)},"num-fmt":function(b){return Ba(b,a,Za)},"html-num":function(b){return Ba(b,a,Ca)},"html-num-fmt":function(b){return Ba(b, -a,Ca,Za)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[Aa(this[l.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return l.ext.internal[a].apply(this,b)}}var l=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new s(Aa(this[v.iApiIndex])):new s(this)};this.fnAddData=function(a,b){var c=this.api(!0), -d=Array.isArray(a)&&(Array.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&ma(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e= -a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()}; -this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a); -(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return Aa(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck= -v.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=v.internal;for(var e in l.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},g=1<d?Ya(e,a,!0):a,j=0,i,e=this.getAttribute("id"),n=!1,m=l.defaults,q=h(this);if("table"!=this.nodeName.toLowerCase())K(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{gb(m);hb(m.column);J(m,m,!0);J(m.column,m.column,!0);J(m,h.extend(g,q.data()),!0);var r=l.settings,j=0;for(i=r.length;j<i;j++){var o=r[j];if(o.nTable== -this||o.nTHead&&o.nTHead.parentNode==this||o.nTFoot&&o.nTFoot.parentNode==this){var s=g.bRetrieve!==k?g.bRetrieve:m.bRetrieve;if(c||s)return o.oInstance;if(g.bDestroy!==k?g.bDestroy:m.bDestroy){o.oInstance.fnDestroy();break}else{K(o,0,"Cannot reinitialise DataTable",3);return}}if(o.sTableId==this.id){r.splice(j,1);break}}if(null===e||""===e)this.id=e="DataTables_Table_"+l.ext._unique++;var p=h.extend(!0,{},l.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:e,sTableId:e});p.nTable=this;p.oApi= -b.internal;p.oInit=g;r.push(p);p.oInstance=1===b.length?b:q.dataTable();gb(g);Ea(g.oLanguage);g.aLengthMenu&&!g.iDisplayLength&&(g.iDisplayLength=Array.isArray(g.aLengthMenu[0])?g.aLengthMenu[0][0]:g.aLengthMenu[0]);g=Ya(h.extend(!0,{},m),g);F(p.oFeatures,g,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));F(p,g,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu", -"sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"]]);F(p.oScroll,g,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);F(p.oLanguage,g,"fnInfoCallback");z(p,"aoDrawCallback",g.fnDrawCallback, -"user");z(p,"aoServerParams",g.fnServerParams,"user");z(p,"aoStateSaveParams",g.fnStateSaveParams,"user");z(p,"aoStateLoadParams",g.fnStateLoadParams,"user");z(p,"aoStateLoaded",g.fnStateLoaded,"user");z(p,"aoRowCallback",g.fnRowCallback,"user");z(p,"aoRowCreatedCallback",g.fnCreatedRow,"user");z(p,"aoHeaderCallback",g.fnHeaderCallback,"user");z(p,"aoFooterCallback",g.fnFooterCallback,"user");z(p,"aoInitComplete",g.fnInitComplete,"user");z(p,"aoPreDrawCallback",g.fnPreDrawCallback,"user");p.rowIdFn= -S(g.rowId);ib(p);var t=p.oClasses;h.extend(t,l.ext.classes,g.oClasses);q.addClass(t.sTable);p.iInitDisplayStart===k&&(p.iInitDisplayStart=g.iDisplayStart,p._iDisplayStart=g.iDisplayStart);null!==g.iDeferLoading&&(p.bDeferLoading=!0,e=Array.isArray(g.iDeferLoading),p._iRecordsDisplay=e?g.iDeferLoading[0]:g.iDeferLoading,p._iRecordsTotal=e?g.iDeferLoading[1]:g.iDeferLoading);var w=p.oLanguage;h.extend(!0,w,g.oLanguage);w.sUrl&&(h.ajax({dataType:"json",url:w.sUrl,success:function(a){Ea(a);J(m.oLanguage, -a);h.extend(true,w,a);ia(p)},error:function(){ia(p)}}),n=!0);null===g.asStripeClasses&&(p.asStripeClasses=[t.sStripeOdd,t.sStripeEven]);var e=p.asStripeClasses,v=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(e,function(a){return v.hasClass(a)}))&&(h("tbody tr",this).removeClass(e.join(" ")),p.asDestroyStripes=e.slice());e=[];r=this.getElementsByTagName("thead");0!==r.length&&(fa(p.aoHeader,r[0]),e=sa(p));if(null===g.aoColumns){r=[];j=0;for(i=e.length;j<i;j++)r.push(null)}else r=g.aoColumns; -j=0;for(i=r.length;j<i;j++)Ga(p,e?e[j]:null);kb(p,g.aoColumnDefs,r,function(a,b){la(p,a,b)});if(v.length){var U=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(v[0]).children("th, td").each(function(a,b){var c=p.aoColumns[a];if(c.mData===a){var d=U(b,"sort")||U(b,"order"),e=U(b,"filter")||U(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};la(p,a)}}})}var V=p.oFeatures,e=function(){if(g.aaSorting=== -k){var a=p.aaSorting;j=0;for(i=a.length;j<i;j++)a[j][1]=p.aoColumns[j].asSorting[0]}ya(p);V.bSort&&z(p,"aoDrawCallback",function(){if(p.bSorted){var a=Y(p),b={};h.each(a,function(a,c){b[c.src]=c.dir});u(p,null,"order",[p,a,b]);Kb(p)}});z(p,"aoDrawCallback",function(){(p.bSorted||y(p)==="ssp"||V.bDeferRender)&&ya(p)},"sc");var a=q.children("caption").each(function(){this._captionSide=h(this).css("caption-side")}),b=q.children("thead");b.length===0&&(b=h("<thead/>").appendTo(q));p.nTHead=b[0];b=q.children("tbody"); -b.length===0&&(b=h("<tbody/>").appendTo(q));p.nTBody=b[0];b=q.children("tfoot");if(b.length===0&&a.length>0&&(p.oScroll.sX!==""||p.oScroll.sY!==""))b=h("<tfoot/>").appendTo(q);if(b.length===0||b.children().length===0)q.addClass(t.sNoFooter);else if(b.length>0){p.nTFoot=b[0];fa(p.aoFooter,p.nTFoot)}if(g.aaData)for(j=0;j<g.aaData.length;j++)O(p,g.aaData[j]);else(p.bDeferLoading||y(p)=="dom")&&oa(p,h(p.nTBody).children("tr"));p.aiDisplay=p.aiDisplayMaster.slice();p.bInitialised=true;n===false&&ia(p)}; -g.bStateSave?(V.bStateSave=!0,z(p,"aoDrawCallback",za,"state_save"),Lb(p,g,e)):e()}});b=null;return this},v,s,o,t,$a={},Ob=/[\r\n\u2028]/g,Ca=/<.*?>/g,bc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,cc=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Za=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb= -function(a,b){$a[b]||($a[b]=RegExp(Sa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace($a[b],"."):a},ab=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Za,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:ab(a.replace(Ca,""),b,c)?!0:null},C=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]); -return d},ka=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},Z=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Sb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},ra=function(a){var b;a:{if(!(2>a.length)){b=a.slice().sort();for(var c=b[0],d=1,e=b.length;d<e;d++){if(b[d]===c){b=!1;break a}c=b[d]}}b=!0}if(b)return a.slice();b=[];var e= -a.length,f,g=0,d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b},Tb=function(a,b){if(Array.isArray(b))for(var c=0;c<b.length;c++)Tb(a,b[c]);else a.push(b);return a};Array.isArray||(Array.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)});String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")});l.util={throttle:function(a,b){var c=b!==k?b:200,d,e;return function(){var b= -this,g=+new Date,j=arguments;if(d&&g<d+c){clearTimeout(e);e=setTimeout(function(){d=k;a.apply(b,j)},c)}else{d=g;a.apply(b,j)}}},escapeRegex:function(a){return a.replace(cc,"\\$1")}};var A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},da=/\[.*?\]$/,X=/\(\)$/,Sa=l.util.escapeRegex,wa=h("<div>")[0],Zb=wa.textContent!==k,ac=/<.*?>/g,Qa=l.util.throttle,Ub=[],x=Array.prototype,dc=function(a){var b,c,d=l.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&a.nodeName.toLowerCase()=== -"table"){b=h.inArray(a,e);return b!==-1?[d[b]]:null}if(a&&typeof a.settings==="function")return a.settings().toArray();typeof a==="string"?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return b!==-1?d[b]:null}).toArray()};s=function(a,b){if(!(this instanceof s))return new s(a,b);var c=[],d=function(a){(a=dc(a))&&c.push.apply(c,a)};if(Array.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=ra(c);b&&h.merge(this,b);this.selector={rows:null, -cols:null,opts:null};s.extend(this,this,Ub)};l.Api=s;h.extend(s.prototype,{any:function(){return this.count()!==0},concat:x.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new s(b[a],this[a]):null},filter:function(a){var b=[];if(x.filter)b=x.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]); -return new s(this.context,b)},flatten:function(){var a=[];return new s(this.context,a.concat.apply(a,this.toArray()))},join:x.join,indexOf:x.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,j,i,h,m=this.context,l,o,t=this.selector;if(typeof a==="string"){d=c;c=b;b=a;a=false}g=0;for(j=m.length;g<j;g++){var u=new s(m[g]);if(b==="table"){f=c.call(u,m[g],g);f!==k&&e.push(f)}else if(b==="columns"||b==="rows"){f=c.call(u, -m[g],this[g],g);f!==k&&e.push(f)}else if(b==="column"||b==="column-rows"||b==="row"||b==="cell"){o=this[g];b==="column-rows"&&(l=Da(m[g],t.opts));i=0;for(h=o.length;i<h;i++){f=o[i];f=b==="cell"?c.call(u,m[g],f.row,f.column,g,i):c.call(u,m[g],f,g,i,l);f!==k&&e.push(f)}}}if(e.length||d){a=new s(m,a?e.concat.apply([],e):e);b=a.selector;b.rows=t.rows;b.cols=t.cols;b.opts=t.opts;return a}return this},lastIndexOf:x.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)}, -length:0,map:function(a){var b=[];if(x.map)b=x.map.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new s(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:x.pop,push:x.push,reduce:x.reduce||function(a,b){return jb(this,a,b,0,this.length,1)},reduceRight:x.reduceRight||function(a,b){return jb(this,a,b,this.length-1,-1,-1)},reverse:x.reverse,selector:null,shift:x.shift,slice:function(){return new s(this.context,this)},sort:x.sort, -splice:x.splice,toArray:function(){return x.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},unique:function(){return new s(this.context,ra(this))},unshift:x.unshift});s.extend=function(a,b,c){if(c.length&&b&&(b instanceof s||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);s.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++){f=c[d];b[f.name]=f.type==="function"?g(a,f.val,f):f.type==="object"?{}:f.val;b[f.name].__dt_wrapper= -true;s.extend(a,b[f.name],f.propExt)}}};s.register=o=function(a,b){if(Array.isArray(a))for(var c=0,d=a.length;c<d;c++)s.register(a[c],b);else for(var e=a.split("."),f=Ub,g,j,c=0,d=e.length;c<d;c++){g=(j=e[c].indexOf("()")!==-1)?e[c].replace("()",""):e[c];var i;a:{i=0;for(var k=f.length;i<k;i++)if(f[i].name===g){i=f[i];break a}i=null}if(!i){i={name:g,val:{},methodExt:[],propExt:[],type:"object"};f.push(i)}if(c===d-1){i.val=b;i.type=typeof b==="function"?"function":h.isPlainObject(b)?"object":"other"}else f= -j?i.methodExt:i.propExt}};s.registerPlural=t=function(a,b,c){s.register(a,c);s.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof s?a.length?Array.isArray(a[0])?new s(a.context,a[0]):a[0]:k:a})};var Vb=function(a,b){if(Array.isArray(a))return h.map(a,function(a){return Vb(a,b)});if(typeof a==="number")return[b[a]];var c=h.map(b,function(a){return a.nTable});return h(c).filter(a).map(function(){var a=h.inArray(this,c);return b[a]}).toArray()};o("tables()",function(a){return a!== -k&&a!==null?new s(Vb(a,this.context)):this});o("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new s(b[0]):a});t("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});t("tables().body()","table().body()",function(){return this.iterator("table",function(a){return a.nTBody},1)});t("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});t("tables().footer()","table().footer()", -function(){return this.iterator("table",function(a){return a.nTFoot},1)});t("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});o("draw()",function(a){return this.iterator("table",function(b){if(a==="page")P(b);else{typeof a==="string"&&(a=a==="full-hold"?false:true);T(b,a===false)}})});o("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Va(b,a)})});o("page.info()",function(){if(this.context.length=== -0)return k;var a=this.context[0],b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=c===-1;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:y(a)==="ssp"}});o("page.len()",function(a){return a===k?this.context.length!==0?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ta(b,a)})});var Wb=function(a,b,c){if(c){var d=new s(a);d.one("draw", -function(){c(d.ajax.json())})}if(y(a)=="ssp")T(a,b);else{D(a,true);var e=a.jqXHR;e&&e.readyState!==4&&e.abort();ta(a,[],function(c){pa(a);for(var c=ua(a,c),d=0,e=c.length;d<e;d++)O(a,c[d]);T(a,b);D(a,false)})}};o("ajax.json()",function(){var a=this.context;if(a.length>0)return a[0].json});o("ajax.params()",function(){var a=this.context;if(a.length>0)return a[0].oAjaxData});o("ajax.reload()",function(a,b){return this.iterator("table",function(c){Wb(c,b===false,a)})});o("ajax.url()",function(a){var b= -this.context;if(a===k){if(b.length===0)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});o("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Wb(c,b===false,a)})});var bb=function(a,b,c,d,e){var f=[],g,j,i,h,m,l;i=typeof b;if(!b||i==="string"||i==="function"||b.length===k)b=[b];i=0;for(h=b.length;i<h;i++){j=b[i]&&b[i].split&&!b[i].match(/[\[\(:]/)? -b[i].split(","):[b[i]];m=0;for(l=j.length;m<l;m++)(g=c(typeof j[m]==="string"?j[m].trim():j[m]))&&g.length&&(f=f.concat(g))}a=v.selector[a];if(a.length){i=0;for(h=a.length;i<h;i++)f=a[i](d,e,f)}return ra(f)},cb=function(a){a||(a={});if(a.filter&&a.search===k)a.search=a.filter;return h.extend({search:"none",order:"current",page:"all"},a)},db=function(a){for(var b=0,c=a.length;b<c;b++)if(a[b].length>0){a[0]=a[b];a[0].length=1;a.length=1;a.context=[a.context[b]];return a}a.length=0;return a},Da=function(a, -b){var c,d,e,f=[],g=a.aiDisplay;e=a.aiDisplayMaster;var j=b.search;c=b.order;d=b.page;if(y(a)=="ssp")return j==="removed"?[]:Z(0,e.length);if(d=="current"){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if(c=="current"||c=="applied")if(j=="none")f=e.slice();else if(j=="applied")f=g.slice();else{if(j=="removed"){var i={};c=0;for(d=g.length;c<d;c++)i[g[c]]=null;f=h.map(e,function(a){return!i.hasOwnProperty(a)?a:null})}}else if(c=="index"||c=="original"){c=0;for(d=a.aoData.length;c< -d;c++)if(j=="none")f.push(c);else{e=h.inArray(c,g);(e===-1&&j=="removed"||e>=0&&j=="applied")&&f.push(c)}}return f};o("rows()",function(a,b){if(a===k)a="";else if(h.isPlainObject(a)){b=a;a=""}var b=cb(b),c=this.iterator("table",function(c){var e=b,f;return bb("row",a,function(a){var b=Pb(a),i=c.aoData;if(b!==null&&!e)return[b];f||(f=Da(c,e));if(b!==null&&h.inArray(b,f)!==-1)return[b];if(a===null||a===k||a==="")return f;if(typeof a==="function")return h.map(f,function(b){var c=i[b];return a(b,c._aData, -c.nTr)?b:null});if(a.nodeName){var b=a._DT_RowIndex,n=a._DT_CellIndex;if(b!==k)return i[b]&&i[b].nTr===a?[b]:[];if(n)return i[n.row]&&i[n.row].nTr===a.parentNode?[n.row]:[];b=h(a).closest("*[data-dt-row]");return b.length?[b.data("dt-row")]:[]}if(typeof a==="string"&&a.charAt(0)==="#"){b=c.aIds[a.replace(/^#/,"")];if(b!==k)return[b.idx]}b=Sb(ka(c.aoData,f,"nTr"));return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});o("rows().nodes()", -function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});o("rows().data()",function(){return this.iterator(true,"rows",function(a,b){return ka(a.aoData,b,"_aData")},1)});t("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return a==="search"?d._aFilterData:d._aSortData},1)});t("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ea(b,c,a)})});t("rows().indexes()","row().index()", -function(){return this.iterator("row",function(a,b){return b},1)});t("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((a===true?"#":"")+h)}return new s(c,b)});t("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c],g,h,i,n,m;e.splice(c,1);g=0;for(h=e.length;g<h;g++){i=e[g];m=i.anCells;if(i.nTr!==null)i.nTr._DT_RowIndex= -g;if(m!==null){i=0;for(n=m.length;i<n;i++)m[i]._DT_CellIndex.row=g}}qa(b.aiDisplayMaster,c);qa(b.aiDisplay,c);qa(a[d],c,false);b._iRecordsDisplay>0&&b._iRecordsDisplay--;Ua(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});o("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++){c=a[f];c.nodeName&&c.nodeName.toUpperCase()==="TR"?h.push(oa(b, -c)[0]):h.push(O(b,c))}return h},1),c=this.rows(-1);c.pop();h.merge(c,b);return c});o("row()",function(a,b){return db(this.rows(a,b))});o("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;var c=b[0].aoData[this[0]];c._aData=a;Array.isArray(a)&&(c.nTr&&c.nTr.id)&&N(b[0].rowId)(a,c.nTr.id);ea(b[0],this[0],"data");return this});o("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null}); -o("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&a.nodeName.toUpperCase()==="TR"?oa(b,a)[0]:O(b,a)});return this.row(b[0])});var eb=function(a,b){var c=a.context;if(c.length)if((c=c[0].aoData[b!==k?b:a[0]])&&c._details){c._details.remove();c._detailsShow=k;c._details=k}},Xb=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach(); -var e=c[0],f=new s(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");if(C(g,"_details").length>0){f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})});f.on("column-visibility.dt.DT_details",function(a,b){if(e===b)for(var c,d=W(b),f=0,h=g.length;f<h;f++){c=g[f];c._details&&c._details.children("td[colspan]").attr("colspan",d)}});f.on("destroy.dt.DT_details", -function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&eb(f,c)})}}}};o("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(a===true)this.child.show();else if(a===false)eb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(Array.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else if(a.nodeName&&a.nodeName.toLowerCase()==="tr")e.push(a);else{c=h("<tr><td></td></tr>").addClass(b); -h("td",c).addClass(b).html(a)[0].colSpan=W(d);e.push(c[0])}};f(a,b);c._details&&c._details.detach();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});o(["row().child.show()","row().child().show()"],function(){Xb(this,true);return this});o(["row().child.hide()","row().child().hide()"],function(){Xb(this,false);return this});o(["row().child.remove()","row().child().remove()"],function(){eb(this);return this});o("row().child.isShown()",function(){var a=this.context;return a.length&& -this.length?a[0].aoData[this[0]]._detailsShow||false:false});var ec=/^([^:]+):(name|visIdx|visible)$/,Yb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));return c};o("columns()",function(a,b){if(a===k)a="";else if(h.isPlainObject(a)){b=a;a=""}var b=cb(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,j=C(g,"sName"),i=C(g,"nTh");return bb("column",e,function(a){var b=Pb(a);if(a==="")return Z(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e= -Da(c,f);return h.map(g,function(b,f){return a(f,Yb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(ec):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var l=h.map(g,function(a,b){return a.bVisible?b:null});return[l[l.length+b]]}return[ba(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length|| -!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});t("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});t("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});t("columns().data()","column().data()",function(){return this.iterator("column-rows",Yb, -1)});t("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});t("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ka(b.aoData,f,a==="search"?"_aFilterData":"_aSortData",c)},1)});t("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ka(a.aoData,e,"anCells",b)},1)});t("columns().visible()","column().visible()", -function(a,b){var c=this,d=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var d=b.aoColumns,j=d[c],i=b.aoData,n,m,l;if(a!==k&&j.bVisible!==a){if(a){var o=h.inArray(true,C(d,"bVisible"),c+1);n=0;for(m=i.length;n<m;n++){l=i[n].nTr;d=i[n].anCells;l&&l.insertBefore(d[c],d[o]||null)}}else h(C(b.aoData,"anCells",c)).detach();j.bVisible=a}});a!==k&&this.iterator("table",function(d){ga(d,d.aoHeader);ga(d,d.aoFooter);d.aiDisplay.length||h(d.nTBody).find("td[colspan]").attr("colspan", -W(d));za(d);c.iterator("column",function(c,d){u(c,null,"column-visibility",[c,d,a,b])});(b===k||b)&&c.columns.adjust()});return d});t("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return a==="visible"?ca(b,c):c},1)});o("columns.adjust()",function(){return this.iterator("table",function(a){aa(a)},1)});o("column.index()",function(a,b){if(this.context.length!==0){var c=this.context[0];if(a==="fromVisible"||a==="toData")return ba(c,b);if(a==="fromData"|| -a==="toVisible")return ca(c,b)}});o("column()",function(a,b){return db(this.columns(a,b))});o("cells()",function(a,b,c){if(h.isPlainObject(a))if(a.row===k){c=a;a=null}else{c=b;b=null}if(h.isPlainObject(b)){c=b;b=null}if(b===null||b===k)return this.iterator("table",function(b){var d=a,e=cb(c),f=b.aoData,g=Da(b,e),i=Sb(ka(f,g,"anCells")),j=h(Tb([],i)),l,n=b.aoColumns.length,o,t,s,u,w,v;return bb("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){o=[];t=0;for(s=g.length;t<s;t++){l= -g[t];for(u=0;u<n;u++){w={row:l,column:u};if(c){v=f[l];a(w,B(b,l,u),v.anCells?v.anCells[u]:null)&&o.push(w)}else o.push(w)}}return o}if(h.isPlainObject(a))return a.column!==k&&a.row!==k&&h.inArray(a.row,g)!==-1?[a]:[];c=j.filter(a).map(function(a,b){return{row:b._DT_CellIndex.row,column:b._DT_CellIndex.column}}).toArray();if(c.length||!a.nodeName)return c;v=h(a).closest("*[data-dt-row]");return v.length?[{row:v.data("dt-row"),column:v.data("dt-column")}]:[]},b,e)});var d=c?{page:c.page,order:c.order, -search:c.search}:{},e=this.columns(b,d),f=this.rows(a,d),g,j,i,l,d=this.iterator("table",function(a,b){var c=[];g=0;for(j=f[b].length;g<j;g++){i=0;for(l=e[b].length;i<l;i++)c.push({row:f[b][g],column:e[b][i]})}return c},1),d=c&&c.selected?this.cells(d,c):d;h.extend(d.selector,{cols:b,rows:a,opts:c});return d});t("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b])&&a.anCells?a.anCells[c]:k},1)});o("cells().data()",function(){return this.iterator("cell", -function(a,b,c){return B(a,b,c)},1)});t("cells().cache()","cell().cache()",function(a){a=a==="search"?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});t("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});t("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,column:c,columnVisible:ca(a,c)}},1)});t("cells().invalidate()","cell().invalidate()", -function(a){return this.iterator("cell",function(b,c,d){ea(b,c,a,d)})});o("cell()",function(a,b,c){return db(this.cells(a,b,c))});o("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;lb(b[0],c[0].row,c[0].column,a);ea(b[0],c[0].row,"data",c[0].column);return this});o("order()",function(a,b){var c=this.context;if(a===k)return c.length!==0?c[0].aaSorting:k;typeof a==="number"?a=[[a,b]]:a.length&&!Array.isArray(a[0])&&(a=Array.prototype.slice.call(arguments)); -return this.iterator("table",function(b){b.aaSorting=a.slice()})});o("order.listener()",function(a,b,c){return this.iterator("table",function(d){Oa(d,a,b,c)})});o("order.fixed()",function(a){if(!a){var b=this.context,b=b.length?b[0].aaSortingFixed:k;return Array.isArray(b)?{pre:b}:b}return this.iterator("table",function(b){b.aaSortingFixed=h.extend(true,{},a)})});o(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b, -c){e.push([c,a])});c.aaSorting=e})});o("search()",function(a,b,c,d){var e=this.context;return a===k?e.length!==0?e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&ha(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:b===null?false:b,bSmart:c===null?true:c,bCaseInsensitive:d===null?true:d}),1)})});t("columns().search()","column().search()",function(a,b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;if(e.oFeatures.bFilter){h.extend(g[f], -{sSearch:a+"",bRegex:b===null?false:b,bSmart:c===null?true:c,bCaseInsensitive:d===null?true:d});ha(e,e.oPreviousSearch,1)}})});o("state()",function(){return this.context.length?this.context[0].oSavedState:null});o("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});o("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});o("state.save()",function(){return this.iterator("table",function(a){za(a)})}); -l.versionCheck=l.fnVersionCheck=function(a){for(var b=l.version.split("."),a=a.split("."),c,d,e=0,f=a.length;e<f;e++){c=parseInt(b[e],10)||0;d=parseInt(a[e],10)||0;if(c!==d)return c>d}return true};l.isDataTable=l.fnIsDataTable=function(a){var b=h(a).get(0),c=false;if(a instanceof l.Api)return true;h.each(l.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:null;if(e.nTable===b||f===b||g===b)c=true});return c};l.tables=l.fnTables= -function(a){var b=false;if(h.isPlainObject(a)){b=a.api;a=a.visible}var c=h.map(l.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new s(c):c};l.camelToHungarian=J;o("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){o(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0]=h.map(a[0].split(/\s/),function(a){return!a.match(/\.dt\b/)?a+".dt":a}).join(" "); -var d=h(this.tables().nodes());d[b].apply(d,a);return this})});o("clear()",function(){return this.iterator("table",function(a){pa(a)})});o("settings()",function(){return new s(this.context,this.context)});o("init()",function(){var a=this.context;return a.length?a[0].oInit:null});o("data()",function(){return this.iterator("table",function(a){return C(a.aoData,"_aData")}).flatten()});o("destroy()",function(a){a=a||false;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses, -e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),m=h.map(b.aoData,function(a){return a.nTr}),o;b.bDestroying=true;u(b,"aoDestroyCallback","destroy",[b]);a||(new s(b)).columns().visible(true);k.off(".DT").find(":not(tbody *)").off(".DT");h(E).off(".DT-"+b.sInstance);if(e!=g.parentNode){i.children("thead").detach();i.append(g)}if(j&&e!=j.parentNode){i.children("tfoot").detach();i.append(j)}b.aaSorting=[];b.aaSortingFixed=[];ya(b);h(m).removeClass(b.asStripeClasses.join(" ")); -h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);f.children().detach();f.append(m);g=a?"remove":"detach";i[g]();k[g]();if(!a&&c){c.insertBefore(e,b.nTableReinsertBefore);i.css("width",b.sDestroyWidth).removeClass(d.sTable);(o=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%o])})}c=h.inArray(b,l.settings);c!==-1&&l.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){o(b+"s().every()", -function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,l){a.call(e[b](g,b==="cell"?h:d,b==="cell"?d:k),g,h,i,l)})})});o("i18n()",function(a,b,c){var d=this.context[0],a=S(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});l.version="1.10.23";l.settings=[];l.models={};l.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};l.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null, -_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};l.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};l.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[], -ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null, -fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((a.iStateDuration===-1?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){return{}}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(a.iStateDuration===-1?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}}, -fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)", -sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},l.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};$(l.defaults);l.defaults.column={aDataSort:null, -iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};$(l.defaults.column);l.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null, -iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[], -aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null, -iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return y(this)=="ssp"?this._iRecordsTotal*1:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return y(this)=="ssp"?this._iRecordsDisplay*1:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures, -f=e.bPaginate;return e.bServerSide?f===false||a===-1?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||a===-1?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};l.ext=v={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:l.fnVersionCheck, -iApiIndex:0,oJUIClasses:{},sVersion:l.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(l.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter", -sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody", -sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Mb=l.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ja(a,b)]},simple_numbers:function(a,b){return["previous",ja(a,b),"next"]},full_numbers:function(a, -b){return["first","previous",ja(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",ja(a,b),"last"]},_numbers:ja,numbers_length:7});h.extend(!0,l.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},l,m,o=0,r=function(b,d){var k,t,s,u,v=g.sPageButtonDisabled,w=function(b){Va(a,b.data.action,true)};k=0;for(t=d.length;k<t;k++){u=d[k];if(Array.isArray(u)){s=h("<"+(u.DT_el||"div")+"/>").appendTo(b);r(s,u)}else{l=null; -m=u;s=a.iTabIndex;switch(u){case "ellipsis":b.append('<span class="ellipsis">…</span>');break;case "first":l=j.sFirst;if(e===0){s=-1;m=m+(" "+v)}break;case "previous":l=j.sPrevious;if(e===0){s=-1;m=m+(" "+v)}break;case "next":l=j.sNext;if(f===0||e===f-1){s=-1;m=m+(" "+v)}break;case "last":l=j.sLast;if(f===0||e===f-1){s=-1;m=m+(" "+v)}break;default:l=a.fnFormatNumber(u+1);m=e===u?g.sPageButtonActive:""}if(l!==null){s=h("<a>",{"class":g.sPageButton+" "+m,"aria-controls":a.sTableId,"aria-label":i[u], -"data-dt-idx":o,tabindex:s,id:c===0&&typeof u==="string"?a.sTableId+"_"+u:null}).html(l).appendTo(b);Xa(s,{action:u},w);o++}}}},t;try{t=h(b).find(H.activeElement).data("dt-idx")}catch(s){}r(h(b).empty(),d);t!==k&&h(b).find("[data-dt-idx="+t+"]").trigger("focus")}}});h.extend(l.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return ab(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&!bc.test(a))return null;var b=Date.parse(a);return b!==null&&!isNaN(b)||M(a)?"date":null},function(a, -b){var c=b.oLanguage.sDecimal;return ab(a,c,true)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,true)?"html-num-fmt"+c:null},function(a){return M(a)||typeof a==="string"&&a.indexOf("<")!==-1?"html":null}]);h.extend(l.ext.type.search,{html:function(a){return M(a)?a:typeof a==="string"?a.replace(Ob," ").replace(Ca,""):""},string:function(a){return M(a)?a:typeof a==="string"?a.replace(Ob," "):a}});var Ba= -function(a,b,c,d){if(a!==0&&(!a||a==="-"))return-Infinity;b&&(a=Qb(a,b));if(a.replace){c&&(a=a.replace(c,""));d&&(a=a.replace(d,""))}return a*1};h.extend(v.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":typeof a==="string"?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a, -b){return a<b?1:a>b?-1:0}});Fa("");h.extend(!0,l.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e, -f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var fb=function(a){return typeof a==="string"?a.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""): -a};l.render={number:function(a,b,c,d,e){return{display:function(f){if(typeof f!=="number"&&typeof f!=="string")return f;var g=f<0?"-":"",h=parseFloat(f);if(isNaN(h))return fb(f);h=h.toFixed(c);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:fb,filter:fb}}};h.extend(l.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ta,_fnAjaxUpdate:nb,_fnAjaxParameters:wb,_fnAjaxUpdateDraw:xb, -_fnAjaxDataSrc:ua,_fnAddColumn:Ga,_fnColumnOptions:la,_fnAdjustColumnSizing:aa,_fnVisibleToColumnIndex:ba,_fnColumnIndexToVisible:ca,_fnVisbleColumns:W,_fnGetColumns:na,_fnColumnTypes:Ia,_fnApplyColumnDefs:kb,_fnHungarianMap:$,_fnCamelToHungarian:J,_fnLanguageCompat:Ea,_fnBrowserDetect:ib,_fnAddData:O,_fnAddTr:oa,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:lb, -_fnSplitObjNotation:La,_fnGetObjectDataFn:S,_fnSetObjectDataFn:N,_fnGetDataMaster:Ma,_fnClearTable:pa,_fnDeleteIndex:qa,_fnInvalidate:ea,_fnGetRowElements:Ka,_fnCreateTr:Ja,_fnBuildHead:mb,_fnDrawHead:ga,_fnDraw:P,_fnReDraw:T,_fnAddOptionsHtml:pb,_fnDetectHeader:fa,_fnGetUniqueThs:sa,_fnFeatureHtmlFilter:rb,_fnFilterComplete:ha,_fnFilterCustom:Ab,_fnFilterColumn:zb,_fnFilter:yb,_fnFilterCreateSearch:Ra,_fnEscapeRegex:Sa,_fnFilterData:Bb,_fnFeatureHtmlInfo:ub,_fnUpdateInfo:Eb,_fnInfoMacros:Fb,_fnInitialise:ia, -_fnInitComplete:va,_fnLengthChange:Ta,_fnFeatureHtmlLength:qb,_fnFeatureHtmlPaginate:vb,_fnPageChange:Va,_fnFeatureHtmlProcessing:sb,_fnProcessingDisplay:D,_fnFeatureHtmlTable:tb,_fnScrollDraw:ma,_fnApplyToChildren:I,_fnCalculateColumnWidths:Ha,_fnThrottle:Qa,_fnConvertToWidth:Gb,_fnGetWidestNode:Hb,_fnGetMaxLenString:Ib,_fnStringToCss:w,_fnSortFlatten:Y,_fnSort:ob,_fnSortAria:Kb,_fnSortListener:Wa,_fnSortAttachListener:Oa,_fnSortingClasses:ya,_fnSortData:Jb,_fnSaveState:za,_fnLoadState:Lb,_fnSettingsFromNode:Aa, -_fnLog:K,_fnMap:F,_fnBindAction:Xa,_fnCallbackReg:z,_fnCallbackFire:u,_fnLengthOverflow:Ua,_fnRenderer:Pa,_fnDataSource:y,_fnRowAttributes:Na,_fnExtend:Ya,_fnCalculateEnd:function(){}});h.fn.dataTable=l;l.$=h;h.fn.dataTableSettings=l.settings;h.fn.dataTableExt=l.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(l,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable}); +/*! DataTables 1.13.4 + * ©2008-2023 SpryMedia Ltd - datatables.net/license + */ +!function(n){"use strict";var a;"function"==typeof define&&define.amd?define(["jquery"],function(t){return n(t,window,document)}):"object"==typeof exports?(a=require("jquery"),"undefined"!=typeof window?module.exports=function(t,e){return t=t||window,e=e||a(t),n(e,t,t.document)}:n(a,window,window.document)):window.DataTable=n(jQuery,window,document)}(function(P,j,y,N){"use strict";function d(t){var e=parseInt(t,10);return!isNaN(e)&&isFinite(t)?e:null}function l(t,e,n){var a=typeof t,r="string"==a;return"number"==a||"bigint"==a||!!h(t)||(e&&r&&(t=G(t,e)),n&&r&&(t=t.replace(q,"")),!isNaN(parseFloat(t))&&isFinite(t))}function a(t,e,n){var a;return!!h(t)||(h(a=t)||"string"==typeof a)&&!!l(t.replace(V,""),e,n)||null}function m(t,e,n,a){var r=[],o=0,i=e.length;if(a!==N)for(;o<i;o++)t[e[o]][n]&&r.push(t[e[o]][n][a]);else for(;o<i;o++)r.push(t[e[o]][n]);return r}function f(t,e){var n,a=[];e===N?(e=0,n=t):(n=e,e=t);for(var r=e;r<n;r++)a.push(r);return a}function _(t){for(var e=[],n=0,a=t.length;n<a;n++)t[n]&&e.push(t[n]);return e}function s(t,e){return-1!==this.indexOf(t,e=e===N?0:e)}var p,e,t,w=function(t,v){if(w.factory(t,v))return w;if(this instanceof w)return P(t).DataTable(v);v=t,this.$=function(t,e){return this.api(!0).$(t,e)},this._=function(t,e){return this.api(!0).rows(t,e).data()},this.api=function(t){return new B(t?ge(this[p.iApiIndex]):this)},this.fnAddData=function(t,e){var n=this.api(!0),t=(Array.isArray(t)&&(Array.isArray(t[0])||P.isPlainObject(t[0]))?n.rows:n.row).add(t);return e!==N&&!e||n.draw(),t.flatten().toArray()},this.fnAdjustColumnSizing=function(t){var e=this.api(!0).columns.adjust(),n=e.settings()[0],a=n.oScroll;t===N||t?e.draw(!1):""===a.sX&&""===a.sY||Qt(n)},this.fnClearTable=function(t){var e=this.api(!0).clear();t!==N&&!t||e.draw()},this.fnClose=function(t){this.api(!0).row(t).child.hide()},this.fnDeleteRow=function(t,e,n){var a=this.api(!0),t=a.rows(t),r=t.settings()[0],o=r.aoData[t[0][0]];return t.remove(),e&&e.call(this,r,o),n!==N&&!n||a.draw(),o},this.fnDestroy=function(t){this.api(!0).destroy(t)},this.fnDraw=function(t){this.api(!0).draw(t)},this.fnFilter=function(t,e,n,a,r,o){var i=this.api(!0);(null===e||e===N?i:i.column(e)).search(t,n,a,o),i.draw()},this.fnGetData=function(t,e){var n,a=this.api(!0);return t!==N?(n=t.nodeName?t.nodeName.toLowerCase():"",e!==N||"td"==n||"th"==n?a.cell(t,e).data():a.row(t).data()||null):a.data().toArray()},this.fnGetNodes=function(t){var e=this.api(!0);return t!==N?e.row(t).node():e.rows().nodes().flatten().toArray()},this.fnGetPosition=function(t){var e=this.api(!0),n=t.nodeName.toUpperCase();return"TR"==n?e.row(t).index():"TD"==n||"TH"==n?[(n=e.cell(t).index()).row,n.columnVisible,n.column]:null},this.fnIsOpen=function(t){return this.api(!0).row(t).child.isShown()},this.fnOpen=function(t,e,n){return this.api(!0).row(t).child(e,n).show().child()[0]},this.fnPageChange=function(t,e){t=this.api(!0).page(t);e!==N&&!e||t.draw(!1)},this.fnSetColumnVis=function(t,e,n){t=this.api(!0).column(t).visible(e);n!==N&&!n||t.columns.adjust().draw()},this.fnSettings=function(){return ge(this[p.iApiIndex])},this.fnSort=function(t){this.api(!0).order(t).draw()},this.fnSortListener=function(t,e,n){this.api(!0).order.listener(t,e,n)},this.fnUpdate=function(t,e,n,a,r){var o=this.api(!0);return(n===N||null===n?o.row(e):o.cell(e,n)).data(t),r!==N&&!r||o.columns.adjust(),a!==N&&!a||o.draw(),0},this.fnVersionCheck=p.fnVersionCheck;var e,y=this,D=v===N,_=this.length;for(e in D&&(v={}),this.oApi=this.internal=p.internal,w.ext.internal)e&&(this[e]=Ge(e));return this.each(function(){var r=1<_?be({},v,!0):v,o=0,t=this.getAttribute("id"),i=!1,e=w.defaults,l=P(this);if("table"!=this.nodeName.toLowerCase())W(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{K(e),Q(e.column),C(e,e,!0),C(e.column,e.column,!0),C(e,P.extend(r,l.data()),!0);for(var n=w.settings,o=0,s=n.length;o<s;o++){var a=n[o];if(a.nTable==this||a.nTHead&&a.nTHead.parentNode==this||a.nTFoot&&a.nTFoot.parentNode==this){var u=(r.bRetrieve!==N?r:e).bRetrieve,c=(r.bDestroy!==N?r:e).bDestroy;if(D||u)return a.oInstance;if(c){a.oInstance.fnDestroy();break}return void W(a,0,"Cannot reinitialise DataTable",3)}if(a.sTableId==this.id){n.splice(o,1);break}}null!==t&&""!==t||(t="DataTables_Table_"+w.ext._unique++,this.id=t);var f,d,h=P.extend(!0,{},w.models.oSettings,{sDestroyWidth:l[0].style.width,sInstance:t,sTableId:t}),p=(h.nTable=this,h.oApi=y.internal,h.oInit=r,n.push(h),h.oInstance=1===y.length?y:l.dataTable(),K(r),Z(r.oLanguage),r.aLengthMenu&&!r.iDisplayLength&&(r.iDisplayLength=(Array.isArray(r.aLengthMenu[0])?r.aLengthMenu[0]:r.aLengthMenu)[0]),r=be(P.extend(!0,{},e),r),F(h.oFeatures,r,["bPaginate","bLengthChange","bFilter","bSort","bSortMulti","bInfo","bProcessing","bAutoWidth","bSortClasses","bServerSide","bDeferRender"]),F(h,r,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"]]),F(h.oScroll,r,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]),F(h.oLanguage,r,"fnInfoCallback"),L(h,"aoDrawCallback",r.fnDrawCallback,"user"),L(h,"aoServerParams",r.fnServerParams,"user"),L(h,"aoStateSaveParams",r.fnStateSaveParams,"user"),L(h,"aoStateLoadParams",r.fnStateLoadParams,"user"),L(h,"aoStateLoaded",r.fnStateLoaded,"user"),L(h,"aoRowCallback",r.fnRowCallback,"user"),L(h,"aoRowCreatedCallback",r.fnCreatedRow,"user"),L(h,"aoHeaderCallback",r.fnHeaderCallback,"user"),L(h,"aoFooterCallback",r.fnFooterCallback,"user"),L(h,"aoInitComplete",r.fnInitComplete,"user"),L(h,"aoPreDrawCallback",r.fnPreDrawCallback,"user"),h.rowIdFn=A(r.rowId),tt(h),h.oClasses),g=(P.extend(p,w.ext.classes,r.oClasses),l.addClass(p.sTable),h.iInitDisplayStart===N&&(h.iInitDisplayStart=r.iDisplayStart,h._iDisplayStart=r.iDisplayStart),null!==r.iDeferLoading&&(h.bDeferLoading=!0,t=Array.isArray(r.iDeferLoading),h._iRecordsDisplay=t?r.iDeferLoading[0]:r.iDeferLoading,h._iRecordsTotal=t?r.iDeferLoading[1]:r.iDeferLoading),h.oLanguage),t=(P.extend(!0,g,r.oLanguage),g.sUrl?(P.ajax({dataType:"json",url:g.sUrl,success:function(t){C(e.oLanguage,t),Z(t),P.extend(!0,g,t,h.oInit.oLanguage),R(h,null,"i18n",[h]),Jt(h)},error:function(){Jt(h)}}),i=!0):R(h,null,"i18n",[h]),null===r.asStripeClasses&&(h.asStripeClasses=[p.sStripeOdd,p.sStripeEven]),h.asStripeClasses),b=l.children("tbody").find("tr").eq(0),m=(-1!==P.inArray(!0,P.map(t,function(t,e){return b.hasClass(t)}))&&(P("tbody tr",this).removeClass(t.join(" ")),h.asDestroyStripes=t.slice()),[]),t=this.getElementsByTagName("thead");if(0!==t.length&&(wt(h.aoHeader,t[0]),m=Ct(h)),null===r.aoColumns)for(f=[],o=0,s=m.length;o<s;o++)f.push(null);else f=r.aoColumns;for(o=0,s=f.length;o<s;o++)nt(h,m?m[o]:null);st(h,r.aoColumnDefs,f,function(t,e){at(h,t,e)}),b.length&&(d=function(t,e){return null!==t.getAttribute("data-"+e)?e:null},P(b[0]).children("th, td").each(function(t,e){var n,a=h.aoColumns[t];a||W(h,0,"Incorrect column count",18),a.mData===t&&(n=d(e,"sort")||d(e,"order"),e=d(e,"filter")||d(e,"search"),null===n&&null===e||(a.mData={_:t+".display",sort:null!==n?t+".@data-"+n:N,type:null!==n?t+".@data-"+n:N,filter:null!==e?t+".@data-"+e:N},a._isArrayHost=!0,at(h,t)))}));var S=h.oFeatures,t=function(){if(r.aaSorting===N){var t=h.aaSorting;for(o=0,s=t.length;o<s;o++)t[o][1]=h.aoColumns[o].asSorting[0]}ce(h),S.bSort&&L(h,"aoDrawCallback",function(){var t,n;h.bSorted&&(t=I(h),n={},P.each(t,function(t,e){n[e.src]=e.dir}),R(h,null,"order",[h,t,n]),le(h))}),L(h,"aoDrawCallback",function(){(h.bSorted||"ssp"===E(h)||S.bDeferRender)&&ce(h)},"sc");var e=l.children("caption").each(function(){this._captionSide=P(this).css("caption-side")}),n=l.children("thead"),a=(0===n.length&&(n=P("<thead/>").appendTo(l)),h.nTHead=n[0],l.children("tbody")),n=(0===a.length&&(a=P("<tbody/>").insertAfter(n)),h.nTBody=a[0],l.children("tfoot"));if(0===(n=0===n.length&&0<e.length&&(""!==h.oScroll.sX||""!==h.oScroll.sY)?P("<tfoot/>").appendTo(l):n).length||0===n.children().length?l.addClass(p.sNoFooter):0<n.length&&(h.nTFoot=n[0],wt(h.aoFooter,h.nTFoot)),r.aaData)for(o=0;o<r.aaData.length;o++)x(h,r.aaData[o]);else!h.bDeferLoading&&"dom"!=E(h)||ut(h,P(h.nTBody).children("tr"));h.aiDisplay=h.aiDisplayMaster.slice(),!(h.bInitialised=!0)===i&&Jt(h)};L(h,"aoDrawCallback",de,"state_save"),r.bStateSave?(S.bStateSave=!0,he(h,0,t)):t()}}),y=null,this},c={},U=/[\r\n\u2028]/g,V=/<.*?>/g,X=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,J=new RegExp("(\\"+["/",".","*","+","?","|","(",")","[","]","{","}","\\","$","^","-"].join("|\\")+")","g"),q=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,h=function(t){return!t||!0===t||"-"===t},G=function(t,e){return c[e]||(c[e]=new RegExp(Ot(e),"g")),"string"==typeof t&&"."!==e?t.replace(/\./g,"").replace(c[e],"."):t},H=function(t,e,n){var a=[],r=0,o=t.length;if(n!==N)for(;r<o;r++)t[r]&&t[r][e]&&a.push(t[r][e][n]);else for(;r<o;r++)t[r]&&a.push(t[r][e]);return a},$=function(t){if(!(t.length<2))for(var e=t.slice().sort(),n=e[0],a=1,r=e.length;a<r;a++){if(e[a]===n)return!1;n=e[a]}return!0},z=function(t){if($(t))return t.slice();var e,n,a,r=[],o=t.length,i=0;t:for(n=0;n<o;n++){for(e=t[n],a=0;a<i;a++)if(r[a]===e)continue t;r.push(e),i++}return r},Y=function(t,e){if(Array.isArray(e))for(var n=0;n<e.length;n++)Y(t,e[n]);else t.push(e);return t};function i(n){var a,r,o={};P.each(n,function(t,e){(a=t.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(a[1]+" ")&&(r=t.replace(a[0],a[2].toLowerCase()),o[r]=t,"o"===a[1])&&i(n[t])}),n._hungarianMap=o}function C(n,a,r){var o;n._hungarianMap||i(n),P.each(a,function(t,e){(o=n._hungarianMap[t])===N||!r&&a[o]!==N||("o"===o.charAt(0)?(a[o]||(a[o]={}),P.extend(!0,a[o],a[t]),C(n[o],a[o],r)):a[o]=a[t])})}function Z(t){var e,n=w.defaults.oLanguage,a=n.sDecimal;a&&Me(a),t&&(e=t.sZeroRecords,!t.sEmptyTable&&e&&"No data available in table"===n.sEmptyTable&&F(t,t,"sZeroRecords","sEmptyTable"),!t.sLoadingRecords&&e&&"Loading..."===n.sLoadingRecords&&F(t,t,"sZeroRecords","sLoadingRecords"),t.sInfoThousands&&(t.sThousands=t.sInfoThousands),e=t.sDecimal)&&a!==e&&Me(e)}Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),Array.prototype.includes||(Array.prototype.includes=s),String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")}),String.prototype.includes||(String.prototype.includes=s),w.util={throttle:function(a,t){var r,o,i=t!==N?t:200;return function(){var t=this,e=+new Date,n=arguments;r&&e<r+i?(clearTimeout(o),o=setTimeout(function(){r=N,a.apply(t,n)},i)):(r=e,a.apply(t,n))}},escapeRegex:function(t){return t.replace(J,"\\$1")},set:function(a){var d;return P.isPlainObject(a)?w.util.set(a._):null===a?function(){}:"function"==typeof a?function(t,e,n){a(t,"set",e,n)}:"string"!=typeof a||-1===a.indexOf(".")&&-1===a.indexOf("[")&&-1===a.indexOf("(")?function(t,e){t[a]=e}:(d=function(t,e,n){for(var a,r,o,i,l=dt(n),n=l[l.length-1],s=0,u=l.length-1;s<u;s++){if("__proto__"===l[s]||"constructor"===l[s])throw new Error("Cannot set prototype values");if(a=l[s].match(ft),r=l[s].match(g),a){if(l[s]=l[s].replace(ft,""),t[l[s]]=[],(a=l.slice()).splice(0,s+1),i=a.join("."),Array.isArray(e))for(var c=0,f=e.length;c<f;c++)d(o={},e[c],i),t[l[s]].push(o);else t[l[s]]=e;return}r&&(l[s]=l[s].replace(g,""),t=t[l[s]](e)),null!==t[l[s]]&&t[l[s]]!==N||(t[l[s]]={}),t=t[l[s]]}n.match(g)?t[n.replace(g,"")](e):t[n.replace(ft,"")]=e},function(t,e){return d(t,e,a)})},get:function(r){var o,d;return P.isPlainObject(r)?(o={},P.each(r,function(t,e){e&&(o[t]=w.util.get(e))}),function(t,e,n,a){var r=o[e]||o._;return r!==N?r(t,e,n,a):t}):null===r?function(t){return t}:"function"==typeof r?function(t,e,n,a){return r(t,e,n,a)}:"string"!=typeof r||-1===r.indexOf(".")&&-1===r.indexOf("[")&&-1===r.indexOf("(")?function(t,e){return t[r]}:(d=function(t,e,n){var a,r,o;if(""!==n)for(var i=dt(n),l=0,s=i.length;l<s;l++){if(f=i[l].match(ft),a=i[l].match(g),f){if(i[l]=i[l].replace(ft,""),""!==i[l]&&(t=t[i[l]]),r=[],i.splice(0,l+1),o=i.join("."),Array.isArray(t))for(var u=0,c=t.length;u<c;u++)r.push(d(t[u],e,o));var f=f[0].substring(1,f[0].length-1);t=""===f?r:r.join(f);break}if(a)i[l]=i[l].replace(g,""),t=t[i[l]]();else{if(null===t||t[i[l]]===N)return N;t=t[i[l]]}}return t},function(t,e){return d(t,e,r)})}};var r=function(t,e,n){t[e]!==N&&(t[n]=t[e])};function K(t){r(t,"ordering","bSort"),r(t,"orderMulti","bSortMulti"),r(t,"orderClasses","bSortClasses"),r(t,"orderCellsTop","bSortCellsTop"),r(t,"order","aaSorting"),r(t,"orderFixed","aaSortingFixed"),r(t,"paging","bPaginate"),r(t,"pagingType","sPaginationType"),r(t,"pageLength","iDisplayLength"),r(t,"searching","bFilter"),"boolean"==typeof t.sScrollX&&(t.sScrollX=t.sScrollX?"100%":""),"boolean"==typeof t.scrollX&&(t.scrollX=t.scrollX?"100%":"");var e=t.aoSearchCols;if(e)for(var n=0,a=e.length;n<a;n++)e[n]&&C(w.models.oSearch,e[n])}function Q(t){r(t,"orderable","bSortable"),r(t,"orderData","aDataSort"),r(t,"orderSequence","asSorting"),r(t,"orderDataType","sortDataType");var e=t.aDataSort;"number"!=typeof e||Array.isArray(e)||(t.aDataSort=[e])}function tt(t){var e,n,a,r;w.__browser||(w.__browser=e={},r=(a=(n=P("<div/>").css({position:"fixed",top:0,left:-1*P(j).scrollLeft(),height:1,width:1,overflow:"hidden"}).append(P("<div/>").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(P("<div/>").css({width:"100%",height:10}))).appendTo("body")).children()).children(),e.barWidth=a[0].offsetWidth-a[0].clientWidth,e.bScrollOversize=100===r[0].offsetWidth&&100!==a[0].clientWidth,e.bScrollbarLeft=1!==Math.round(r.offset().left),e.bBounding=!!n[0].getBoundingClientRect().width,n.remove()),P.extend(t.oBrowser,w.__browser),t.oScroll.iBarWidth=w.__browser.barWidth}function et(t,e,n,a,r,o){var i,l=a,s=!1;for(n!==N&&(i=n,s=!0);l!==r;)t.hasOwnProperty(l)&&(i=s?e(i,t[l],l,t):t[l],s=!0,l+=o);return i}function nt(t,e){var n=w.defaults.column,a=t.aoColumns.length,n=P.extend({},w.models.oColumn,n,{nTh:e||y.createElement("th"),sTitle:n.sTitle||(e?e.innerHTML:""),aDataSort:n.aDataSort||[a],mData:n.mData||a,idx:a}),n=(t.aoColumns.push(n),t.aoPreSearchCols);n[a]=P.extend({},w.models.oSearch,n[a]),at(t,a,P(e).data())}function at(t,e,n){function a(t){return"string"==typeof t&&-1!==t.indexOf("@")}var e=t.aoColumns[e],r=t.oClasses,o=P(e.nTh),i=(!e.sWidthOrig&&(e.sWidthOrig=o.attr("width")||null,u=(o.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/))&&(e.sWidthOrig=u[1]),n!==N&&null!==n&&(Q(n),C(w.defaults.column,n,!0),n.mDataProp===N||n.mData||(n.mData=n.mDataProp),n.sType&&(e._sManualType=n.sType),n.className&&!n.sClass&&(n.sClass=n.className),n.sClass&&o.addClass(n.sClass),u=e.sClass,P.extend(e,n),F(e,n,"sWidth","sWidthOrig"),u!==e.sClass&&(e.sClass=u+" "+e.sClass),n.iDataSort!==N&&(e.aDataSort=[n.iDataSort]),F(e,n,"aDataSort")),e.mData),l=A(i),s=e.mRender?A(e.mRender):null,u=(e._bAttrSrc=P.isPlainObject(i)&&(a(i.sort)||a(i.type)||a(i.filter)),e._setter=null,e.fnGetData=function(t,e,n){var a=l(t,e,N,n);return s&&e?s(a,e,t,n):a},e.fnSetData=function(t,e,n){return b(i)(t,e,n)},"number"==typeof i||e._isArrayHost||(t._rowReadObject=!0),t.oFeatures.bSort||(e.bSortable=!1,o.addClass(r.sSortableNone)),-1!==P.inArray("asc",e.asSorting)),n=-1!==P.inArray("desc",e.asSorting);e.bSortable&&(u||n)?u&&!n?(e.sSortingClass=r.sSortableAsc,e.sSortingClassJUI=r.sSortJUIAscAllowed):!u&&n?(e.sSortingClass=r.sSortableDesc,e.sSortingClassJUI=r.sSortJUIDescAllowed):(e.sSortingClass=r.sSortable,e.sSortingClassJUI=r.sSortJUI):(e.sSortingClass=r.sSortableNone,e.sSortingClassJUI="")}function O(t){if(!1!==t.oFeatures.bAutoWidth){var e=t.aoColumns;ee(t);for(var n=0,a=e.length;n<a;n++)e[n].nTh.style.width=e[n].sWidth}var r=t.oScroll;""===r.sY&&""===r.sX||Qt(t),R(t,null,"column-sizing",[t])}function rt(t,e){t=it(t,"bVisible");return"number"==typeof t[e]?t[e]:null}function ot(t,e){t=it(t,"bVisible"),e=P.inArray(e,t);return-1!==e?e:null}function T(t){var n=0;return P.each(t.aoColumns,function(t,e){e.bVisible&&"none"!==P(e.nTh).css("display")&&n++}),n}function it(t,n){var a=[];return P.map(t.aoColumns,function(t,e){t[n]&&a.push(e)}),a}function lt(t){for(var e,n,a,r,o,i,l,s=t.aoColumns,u=t.aoData,c=w.ext.type.detect,f=0,d=s.length;f<d;f++)if(l=[],!(o=s[f]).sType&&o._sManualType)o.sType=o._sManualType;else if(!o.sType){for(e=0,n=c.length;e<n;e++){for(a=0,r=u.length;a<r&&(l[a]===N&&(l[a]=S(t,a,f,"type")),(i=c[e](l[a],t))||e===c.length-1)&&("html"!==i||h(l[a]));a++);if(i){o.sType=i;break}}o.sType||(o.sType="string")}}function st(t,e,n,a){var r,o,i,l,s=t.aoColumns;if(e)for(r=e.length-1;0<=r;r--)for(var u,c=(u=e[r]).target!==N?u.target:u.targets!==N?u.targets:u.aTargets,f=0,d=(c=Array.isArray(c)?c:[c]).length;f<d;f++)if("number"==typeof c[f]&&0<=c[f]){for(;s.length<=c[f];)nt(t);a(c[f],u)}else if("number"==typeof c[f]&&c[f]<0)a(s.length+c[f],u);else if("string"==typeof c[f])for(i=0,l=s.length;i<l;i++)"_all"!=c[f]&&!P(s[i].nTh).hasClass(c[f])||a(i,u);if(n)for(r=0,o=n.length;r<o;r++)a(r,n[r])}function x(t,e,n,a){for(var r=t.aoData.length,o=P.extend(!0,{},w.models.oRow,{src:n?"dom":"data",idx:r}),i=(o._aData=e,t.aoData.push(o),t.aoColumns),l=0,s=i.length;l<s;l++)i[l].sType=null;t.aiDisplayMaster.push(r);e=t.rowIdFn(e);return e!==N&&(t.aIds[e]=o),!n&&t.oFeatures.bDeferRender||St(t,r,n,a),r}function ut(n,t){var a;return(t=t instanceof P?t:P(t)).map(function(t,e){return a=mt(n,e),x(n,a.data,e,a.cells)})}function S(t,e,n,a){"search"===a?a="filter":"order"===a&&(a="sort");var r=t.iDraw,o=t.aoColumns[n],i=t.aoData[e]._aData,l=o.sDefaultContent,s=o.fnGetData(i,a,{settings:t,row:e,col:n});if(s===N)return t.iDrawError!=r&&null===l&&(W(t,0,"Requested unknown parameter "+("function"==typeof o.mData?"{function}":"'"+o.mData+"'")+" for row "+e+", column "+n,4),t.iDrawError=r),l;if(s!==i&&null!==s||null===l||a===N){if("function"==typeof s)return s.call(i)}else s=l;return null===s&&"display"===a?"":"filter"===a&&(e=w.ext.type.search)[o.sType]?e[o.sType](s):s}function ct(t,e,n,a){var r=t.aoColumns[n],o=t.aoData[e]._aData;r.fnSetData(o,a,{settings:t,row:e,col:n})}var ft=/\[.*?\]$/,g=/\(\)$/;function dt(t){return P.map(t.match(/(\\.|[^\.])+/g)||[""],function(t){return t.replace(/\\\./g,".")})}var A=w.util.get,b=w.util.set;function ht(t){return H(t.aoData,"_aData")}function pt(t){t.aoData.length=0,t.aiDisplayMaster.length=0,t.aiDisplay.length=0,t.aIds={}}function gt(t,e,n){for(var a=-1,r=0,o=t.length;r<o;r++)t[r]==e?a=r:t[r]>e&&t[r]--;-1!=a&&n===N&&t.splice(a,1)}function bt(n,a,t,e){function r(t,e){for(;t.childNodes.length;)t.removeChild(t.firstChild);t.innerHTML=S(n,a,e,"display")}var o,i,l=n.aoData[a];if("dom"!==t&&(t&&"auto"!==t||"dom"!==l.src)){var s=l.anCells;if(s)if(e!==N)r(s[e],e);else for(o=0,i=s.length;o<i;o++)r(s[o],o)}else l._aData=mt(n,l,e,e===N?N:l._aData).data;l._aSortData=null,l._aFilterData=null;var u=n.aoColumns;if(e!==N)u[e].sType=null;else{for(o=0,i=u.length;o<i;o++)u[o].sType=null;vt(n,l)}}function mt(t,e,n,a){function r(t,e){var n;"string"==typeof t&&-1!==(n=t.indexOf("@"))&&(n=t.substring(n+1),b(t)(a,e.getAttribute(n)))}function o(t){n!==N&&n!==f||(l=d[f],s=t.innerHTML.trim(),l&&l._bAttrSrc?(b(l.mData._)(a,s),r(l.mData.sort,t),r(l.mData.type,t),r(l.mData.filter,t)):h?(l._setter||(l._setter=b(l.mData)),l._setter(a,s)):a[f]=s),f++}var i,l,s,u=[],c=e.firstChild,f=0,d=t.aoColumns,h=t._rowReadObject;a=a!==N?a:h?{}:[];if(c)for(;c;)"TD"!=(i=c.nodeName.toUpperCase())&&"TH"!=i||(o(c),u.push(c)),c=c.nextSibling;else for(var p=0,g=(u=e.anCells).length;p<g;p++)o(u[p]);var e=e.firstChild?e:e.nTr;return e&&(e=e.getAttribute("id"))&&b(t.rowId)(a,e),{data:a,cells:u}}function St(t,e,n,a){var r,o,i,l,s,u,c=t.aoData[e],f=c._aData,d=[];if(null===c.nTr){for(r=n||y.createElement("tr"),c.nTr=r,c.anCells=d,r._DT_RowIndex=e,vt(t,c),l=0,s=t.aoColumns.length;l<s;l++)i=t.aoColumns[l],(o=(u=!n)?y.createElement(i.sCellType):a[l])||W(t,0,"Incorrect column count",18),o._DT_CellIndex={row:e,column:l},d.push(o),!u&&(!i.mRender&&i.mData===l||P.isPlainObject(i.mData)&&i.mData._===l+".display")||(o.innerHTML=S(t,e,l,"display")),i.sClass&&(o.className+=" "+i.sClass),i.bVisible&&!n?r.appendChild(o):!i.bVisible&&n&&o.parentNode.removeChild(o),i.fnCreatedCell&&i.fnCreatedCell.call(t.oInstance,o,S(t,e,l),f,e,l);R(t,"aoRowCreatedCallback",null,[r,f,e,d])}}function vt(t,e){var n=e.nTr,a=e._aData;n&&((t=t.rowIdFn(a))&&(n.id=t),a.DT_RowClass&&(t=a.DT_RowClass.split(" "),e.__rowc=e.__rowc?z(e.__rowc.concat(t)):t,P(n).removeClass(e.__rowc.join(" ")).addClass(a.DT_RowClass)),a.DT_RowAttr&&P(n).attr(a.DT_RowAttr),a.DT_RowData)&&P(n).data(a.DT_RowData)}function yt(t){var e,n,a,r=t.nTHead,o=t.nTFoot,i=0===P("th, td",r).length,l=t.oClasses,s=t.aoColumns;for(i&&(n=P("<tr/>").appendTo(r)),c=0,f=s.length;c<f;c++)a=s[c],e=P(a.nTh).addClass(a.sClass),i&&e.appendTo(n),t.oFeatures.bSort&&(e.addClass(a.sSortingClass),!1!==a.bSortable)&&(e.attr("tabindex",t.iTabIndex).attr("aria-controls",t.sTableId),ue(t,a.nTh,c)),a.sTitle!=e[0].innerHTML&&e.html(a.sTitle),ve(t,"header")(t,e,a,l);if(i&&wt(t.aoHeader,r),P(r).children("tr").children("th, td").addClass(l.sHeaderTH),P(o).children("tr").children("th, td").addClass(l.sFooterTH),null!==o)for(var u=t.aoFooter[0],c=0,f=u.length;c<f;c++)(a=s[c])?(a.nTf=u[c].cell,a.sClass&&P(a.nTf).addClass(a.sClass)):W(t,0,"Incorrect column count",18)}function Dt(t,e,n){var a,r,o,i,l,s,u,c,f,d=[],h=[],p=t.aoColumns.length;if(e){for(n===N&&(n=!1),a=0,r=e.length;a<r;a++){for(d[a]=e[a].slice(),d[a].nTr=e[a].nTr,o=p-1;0<=o;o--)t.aoColumns[o].bVisible||n||d[a].splice(o,1);h.push([])}for(a=0,r=d.length;a<r;a++){if(u=d[a].nTr)for(;s=u.firstChild;)u.removeChild(s);for(o=0,i=d[a].length;o<i;o++)if(f=c=1,h[a][o]===N){for(u.appendChild(d[a][o].cell),h[a][o]=1;d[a+c]!==N&&d[a][o].cell==d[a+c][o].cell;)h[a+c][o]=1,c++;for(;d[a][o+f]!==N&&d[a][o].cell==d[a][o+f].cell;){for(l=0;l<c;l++)h[a+l][o+f]=1;f++}P(d[a][o].cell).attr("rowspan",c).attr("colspan",f)}}}}function v(t,e){n="ssp"==E(s=t),(l=s.iInitDisplayStart)!==N&&-1!==l&&(s._iDisplayStart=!n&&l>=s.fnRecordsDisplay()?0:l,s.iInitDisplayStart=-1);var n=R(t,"aoPreDrawCallback","preDraw",[t]);if(-1!==P.inArray(!1,n))D(t,!1);else{var a=[],r=0,o=t.asStripeClasses,i=o.length,l=t.oLanguage,s="ssp"==E(t),u=t.aiDisplay,n=t._iDisplayStart,c=t.fnDisplayEnd();if(t.bDrawing=!0,t.bDeferLoading)t.bDeferLoading=!1,t.iDraw++,D(t,!1);else if(s){if(!t.bDestroying&&!e)return void xt(t)}else t.iDraw++;if(0!==u.length)for(var f=s?t.aoData.length:c,d=s?0:n;d<f;d++){var h,p=u[d],g=t.aoData[p],b=(null===g.nTr&&St(t,p),g.nTr);0!==i&&(h=o[r%i],g._sRowStripe!=h)&&(P(b).removeClass(g._sRowStripe).addClass(h),g._sRowStripe=h),R(t,"aoRowCallback",null,[b,g._aData,r,d,p]),a.push(b),r++}else{e=l.sZeroRecords;1==t.iDraw&&"ajax"==E(t)?e=l.sLoadingRecords:l.sEmptyTable&&0===t.fnRecordsTotal()&&(e=l.sEmptyTable),a[0]=P("<tr/>",{class:i?o[0]:""}).append(P("<td />",{valign:"top",colSpan:T(t),class:t.oClasses.sRowEmpty}).html(e))[0]}R(t,"aoHeaderCallback","header",[P(t.nTHead).children("tr")[0],ht(t),n,c,u]),R(t,"aoFooterCallback","footer",[P(t.nTFoot).children("tr")[0],ht(t),n,c,u]);s=P(t.nTBody);s.children().detach(),s.append(P(a)),R(t,"aoDrawCallback","draw",[t]),t.bSorted=!1,t.bFiltered=!1,t.bDrawing=!1}}function u(t,e){var n=t.oFeatures,a=n.bSort,n=n.bFilter;a&&ie(t),n?Rt(t,t.oPreviousSearch):t.aiDisplay=t.aiDisplayMaster.slice(),!0!==e&&(t._iDisplayStart=0),t._drawHold=e,v(t),t._drawHold=!1}function _t(t){for(var e,n,a,r,o,i,l,s=t.oClasses,u=P(t.nTable),u=P("<div/>").insertBefore(u),c=t.oFeatures,f=P("<div/>",{id:t.sTableId+"_wrapper",class:s.sWrapper+(t.nTFoot?"":" "+s.sNoFooter)}),d=(t.nHolding=u[0],t.nTableWrapper=f[0],t.nTableReinsertBefore=t.nTable.nextSibling,t.sDom.split("")),h=0;h<d.length;h++){if(e=null,"<"==(n=d[h])){if(a=P("<div/>")[0],"'"==(r=d[h+1])||'"'==r){for(o="",i=2;d[h+i]!=r;)o+=d[h+i],i++;"H"==o?o=s.sJUIHeader:"F"==o&&(o=s.sJUIFooter),-1!=o.indexOf(".")?(l=o.split("."),a.id=l[0].substr(1,l[0].length-1),a.className=l[1]):"#"==o.charAt(0)?a.id=o.substr(1,o.length-1):a.className=o,h+=i}f.append(a),f=P(a)}else if(">"==n)f=f.parent();else if("l"==n&&c.bPaginate&&c.bLengthChange)e=$t(t);else if("f"==n&&c.bFilter)e=Lt(t);else if("r"==n&&c.bProcessing)e=Zt(t);else if("t"==n)e=Kt(t);else if("i"==n&&c.bInfo)e=Ut(t);else if("p"==n&&c.bPaginate)e=zt(t);else if(0!==w.ext.feature.length)for(var p=w.ext.feature,g=0,b=p.length;g<b;g++)if(n==p[g].cFeature){e=p[g].fnInit(t);break}e&&((l=t.aanFeatures)[n]||(l[n]=[]),l[n].push(e),f.append(e))}u.replaceWith(f),t.nHolding=null}function wt(t,e){var n,a,r,o,i,l,s,u,c,f,d=P(e).children("tr");for(t.splice(0,t.length),r=0,l=d.length;r<l;r++)t.push([]);for(r=0,l=d.length;r<l;r++)for(a=(n=d[r]).firstChild;a;){if("TD"==a.nodeName.toUpperCase()||"TH"==a.nodeName.toUpperCase())for(u=(u=+a.getAttribute("colspan"))&&0!=u&&1!=u?u:1,c=(c=+a.getAttribute("rowspan"))&&0!=c&&1!=c?c:1,s=function(t,e,n){for(var a=t[e];a[n];)n++;return n}(t,r,0),f=1==u,i=0;i<u;i++)for(o=0;o<c;o++)t[r+o][s+i]={cell:a,unique:f},t[r+o].nTr=n;a=a.nextSibling}}function Ct(t,e,n){var a=[];n||(n=t.aoHeader,e&&wt(n=[],e));for(var r=0,o=n.length;r<o;r++)for(var i=0,l=n[r].length;i<l;i++)!n[r][i].unique||a[i]&&t.bSortCellsTop||(a[i]=n[r][i].cell);return a}function Tt(r,t,n){function e(t){var e=r.jqXHR?r.jqXHR.status:null;(null===t||"number"==typeof e&&204==e)&&Ft(r,t={},[]),(e=t.error||t.sError)&&W(r,0,e),r.json=t,R(r,null,"xhr",[r,t,r.jqXHR]),n(t)}R(r,"aoServerParams","serverParams",[t]),t&&Array.isArray(t)&&(a={},o=/(.*?)\[\]$/,P.each(t,function(t,e){var n=e.name.match(o);n?(n=n[0],a[n]||(a[n]=[]),a[n].push(e.value)):a[e.name]=e.value}),t=a);var a,o,i,l=r.ajax,s=r.oInstance,u=(P.isPlainObject(l)&&l.data&&(u="function"==typeof(i=l.data)?i(t,r):i,t="function"==typeof i&&u?u:P.extend(!0,t,u),delete l.data),{data:t,success:e,dataType:"json",cache:!1,type:r.sServerMethod,error:function(t,e,n){var a=R(r,null,"xhr",[r,null,r.jqXHR]);-1===P.inArray(!0,a)&&("parsererror"==e?W(r,0,"Invalid JSON response",1):4===t.readyState&&W(r,0,"Ajax error",7)),D(r,!1)}});r.oAjaxData=t,R(r,null,"preXhr",[r,t]),r.fnServerData?r.fnServerData.call(s,r.sAjaxSource,P.map(t,function(t,e){return{name:e,value:t}}),e,r):r.sAjaxSource||"string"==typeof l?r.jqXHR=P.ajax(P.extend(u,{url:l||r.sAjaxSource})):"function"==typeof l?r.jqXHR=l.call(s,t,e,r):(r.jqXHR=P.ajax(P.extend(u,l)),l.data=i)}function xt(e){e.iDraw++,D(e,!0),Tt(e,At(e),function(t){It(e,t)})}function At(t){for(var e,n,a,r=t.aoColumns,o=r.length,i=t.oFeatures,l=t.oPreviousSearch,s=t.aoPreSearchCols,u=[],c=I(t),f=t._iDisplayStart,d=!1!==i.bPaginate?t._iDisplayLength:-1,h=function(t,e){u.push({name:t,value:e})},p=(h("sEcho",t.iDraw),h("iColumns",o),h("sColumns",H(r,"sName").join(",")),h("iDisplayStart",f),h("iDisplayLength",d),{draw:t.iDraw,columns:[],order:[],start:f,length:d,search:{value:l.sSearch,regex:l.bRegex}}),g=0;g<o;g++)n=r[g],a=s[g],e="function"==typeof n.mData?"function":n.mData,p.columns.push({data:e,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:a.sSearch,regex:a.bRegex}}),h("mDataProp_"+g,e),i.bFilter&&(h("sSearch_"+g,a.sSearch),h("bRegex_"+g,a.bRegex),h("bSearchable_"+g,n.bSearchable)),i.bSort&&h("bSortable_"+g,n.bSortable);i.bFilter&&(h("sSearch",l.sSearch),h("bRegex",l.bRegex)),i.bSort&&(P.each(c,function(t,e){p.order.push({column:e.col,dir:e.dir}),h("iSortCol_"+t,e.col),h("sSortDir_"+t,e.dir)}),h("iSortingCols",c.length));f=w.ext.legacy.ajax;return null===f?t.sAjaxSource?u:p:f?u:p}function It(t,n){function e(t,e){return n[t]!==N?n[t]:n[e]}var a=Ft(t,n),r=e("sEcho","draw"),o=e("iTotalRecords","recordsTotal"),i=e("iTotalDisplayRecords","recordsFiltered");if(r!==N){if(+r<t.iDraw)return;t.iDraw=+r}a=a||[],pt(t),t._iRecordsTotal=parseInt(o,10),t._iRecordsDisplay=parseInt(i,10);for(var l=0,s=a.length;l<s;l++)x(t,a[l]);t.aiDisplay=t.aiDisplayMaster.slice(),v(t,!0),t._bInitComplete||qt(t,n),D(t,!1)}function Ft(t,e,n){t=P.isPlainObject(t.ajax)&&t.ajax.dataSrc!==N?t.ajax.dataSrc:t.sAjaxDataProp;if(!n)return"data"===t?e.aaData||e[t]:""!==t?A(t)(e):e;b(t)(e,n)}function Lt(n){function e(t){i.f;var e=this.value||"";o.return&&"Enter"!==t.key||e!=o.sSearch&&(Rt(n,{sSearch:e,bRegex:o.bRegex,bSmart:o.bSmart,bCaseInsensitive:o.bCaseInsensitive,return:o.return}),n._iDisplayStart=0,v(n))}var t=n.oClasses,a=n.sTableId,r=n.oLanguage,o=n.oPreviousSearch,i=n.aanFeatures,l='<input type="search" class="'+t.sFilterInput+'"/>',s=(s=r.sSearch).match(/_INPUT_/)?s.replace("_INPUT_",l):s+l,l=P("<div/>",{id:i.f?null:a+"_filter",class:t.sFilter}).append(P("<label/>").append(s)),t=null!==n.searchDelay?n.searchDelay:"ssp"===E(n)?400:0,u=P("input",l).val(o.sSearch).attr("placeholder",r.sSearchPlaceholder).on("keyup.DT search.DT input.DT paste.DT cut.DT",t?ne(e,t):e).on("mouseup",function(t){setTimeout(function(){e.call(u[0],t)},10)}).on("keypress.DT",function(t){if(13==t.keyCode)return!1}).attr("aria-controls",a);return P(n.nTable).on("search.dt.DT",function(t,e){if(n===e)try{u[0]!==y.activeElement&&u.val(o.sSearch)}catch(t){}}),l[0]}function Rt(t,e,n){function a(t){o.sSearch=t.sSearch,o.bRegex=t.bRegex,o.bSmart=t.bSmart,o.bCaseInsensitive=t.bCaseInsensitive,o.return=t.return}function r(t){return t.bEscapeRegex!==N?!t.bEscapeRegex:t.bRegex}var o=t.oPreviousSearch,i=t.aoPreSearchCols;if(lt(t),"ssp"!=E(t)){Nt(t,e.sSearch,n,r(e),e.bSmart,e.bCaseInsensitive,e.return),a(e);for(var l=0;l<i.length;l++)jt(t,i[l].sSearch,l,r(i[l]),i[l].bSmart,i[l].bCaseInsensitive);Pt(t)}else a(e);t.bFiltered=!0,R(t,null,"search",[t])}function Pt(t){for(var e,n,a=w.ext.search,r=t.aiDisplay,o=0,i=a.length;o<i;o++){for(var l=[],s=0,u=r.length;s<u;s++)n=r[s],e=t.aoData[n],a[o](t,e._aFilterData,n,e._aData,s)&&l.push(n);r.length=0,P.merge(r,l)}}function jt(t,e,n,a,r,o){if(""!==e){for(var i,l=[],s=t.aiDisplay,u=Ht(e,a,r,o),c=0;c<s.length;c++)i=t.aoData[s[c]]._aFilterData[n],u.test(i)&&l.push(s[c]);t.aiDisplay=l}}function Nt(t,e,n,a,r,o){var i,l,s,u=Ht(e,a,r,o),r=t.oPreviousSearch.sSearch,o=t.aiDisplayMaster,c=[];if(0!==w.ext.search.length&&(n=!0),l=Wt(t),e.length<=0)t.aiDisplay=o.slice();else{for((l||n||a||r.length>e.length||0!==e.indexOf(r)||t.bSorted)&&(t.aiDisplay=o.slice()),i=t.aiDisplay,s=0;s<i.length;s++)u.test(t.aoData[i[s]]._sFilterRow)&&c.push(i[s]);t.aiDisplay=c}}function Ht(t,e,n,a){return t=e?t:Ot(t),n&&(t="^(?=.*?"+P.map(t.match(/"[^"]+"|[^ ]+/g)||[""],function(t){var e;return(t='"'===t.charAt(0)?(e=t.match(/^"(.*)"$/))?e[1]:t:t).replace('"',"")}).join(")(?=.*?")+").*$"),new RegExp(t,a?"i":"")}var Ot=w.util.escapeRegex,kt=P("<div>")[0],Mt=kt.textContent!==N;function Wt(t){for(var e,n,a,r,o,i=t.aoColumns,l=!1,s=0,u=t.aoData.length;s<u;s++)if(!(o=t.aoData[s])._aFilterData){for(a=[],e=0,n=i.length;e<n;e++)i[e].bSearchable?"string"!=typeof(r=null===(r=S(t,s,e,"filter"))?"":r)&&r.toString&&(r=r.toString()):r="",r.indexOf&&-1!==r.indexOf("&")&&(kt.innerHTML=r,r=Mt?kt.textContent:kt.innerText),r.replace&&(r=r.replace(/[\r\n\u2028]/g,"")),a.push(r);o._aFilterData=a,o._sFilterRow=a.join(" "),l=!0}return l}function Et(t){return{search:t.sSearch,smart:t.bSmart,regex:t.bRegex,caseInsensitive:t.bCaseInsensitive}}function Bt(t){return{sSearch:t.search,bSmart:t.smart,bRegex:t.regex,bCaseInsensitive:t.caseInsensitive}}function Ut(t){var e=t.sTableId,n=t.aanFeatures.i,a=P("<div/>",{class:t.oClasses.sInfo,id:n?null:e+"_info"});return n||(t.aoDrawCallback.push({fn:Vt,sName:"information"}),a.attr("role","status").attr("aria-live","polite"),P(t.nTable).attr("aria-describedby",e+"_info")),a[0]}function Vt(t){var e,n,a,r,o,i,l=t.aanFeatures.i;0!==l.length&&(i=t.oLanguage,e=t._iDisplayStart+1,n=t.fnDisplayEnd(),a=t.fnRecordsTotal(),o=(r=t.fnRecordsDisplay())?i.sInfo:i.sInfoEmpty,r!==a&&(o+=" "+i.sInfoFiltered),o=Xt(t,o+=i.sInfoPostFix),null!==(i=i.fnInfoCallback)&&(o=i.call(t.oInstance,t,e,n,a,r,o)),P(l).html(o))}function Xt(t,e){var n=t.fnFormatNumber,a=t._iDisplayStart+1,r=t._iDisplayLength,o=t.fnRecordsDisplay(),i=-1===r;return e.replace(/_START_/g,n.call(t,a)).replace(/_END_/g,n.call(t,t.fnDisplayEnd())).replace(/_MAX_/g,n.call(t,t.fnRecordsTotal())).replace(/_TOTAL_/g,n.call(t,o)).replace(/_PAGE_/g,n.call(t,i?1:Math.ceil(a/r))).replace(/_PAGES_/g,n.call(t,i?1:Math.ceil(o/r)))}function Jt(n){var a,t,e,r=n.iInitDisplayStart,o=n.aoColumns,i=n.oFeatures,l=n.bDeferLoading;if(n.bInitialised){for(_t(n),yt(n),Dt(n,n.aoHeader),Dt(n,n.aoFooter),D(n,!0),i.bAutoWidth&&ee(n),a=0,t=o.length;a<t;a++)(e=o[a]).sWidth&&(e.nTh.style.width=M(e.sWidth));R(n,null,"preInit",[n]),u(n);i=E(n);"ssp"==i&&!l||("ajax"==i?Tt(n,[],function(t){var e=Ft(n,t);for(a=0;a<e.length;a++)x(n,e[a]);n.iInitDisplayStart=r,u(n),D(n,!1),qt(n,t)}):(D(n,!1),qt(n)))}else setTimeout(function(){Jt(n)},200)}function qt(t,e){t._bInitComplete=!0,(e||t.oInit.aaData)&&O(t),R(t,null,"plugin-init",[t,e]),R(t,"aoInitComplete","init",[t,e])}function Gt(t,e){e=parseInt(e,10);t._iDisplayLength=e,Se(t),R(t,null,"length",[t,e])}function $t(a){for(var t=a.oClasses,e=a.sTableId,n=a.aLengthMenu,r=Array.isArray(n[0]),o=r?n[0]:n,i=r?n[1]:n,l=P("<select/>",{name:e+"_length","aria-controls":e,class:t.sLengthSelect}),s=0,u=o.length;s<u;s++)l[0][s]=new Option("number"==typeof i[s]?a.fnFormatNumber(i[s]):i[s],o[s]);var c=P("<div><label/></div>").addClass(t.sLength);return a.aanFeatures.l||(c[0].id=e+"_length"),c.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",l[0].outerHTML)),P("select",c).val(a._iDisplayLength).on("change.DT",function(t){Gt(a,P(this).val()),v(a)}),P(a.nTable).on("length.dt.DT",function(t,e,n){a===e&&P("select",c).val(n)}),c[0]}function zt(t){function c(t){v(t)}var e=t.sPaginationType,f=w.ext.pager[e],d="function"==typeof f,e=P("<div/>").addClass(t.oClasses.sPaging+e)[0],h=t.aanFeatures;return d||f.fnInit(t,e,c),h.p||(e.id=t.sTableId+"_paginate",t.aoDrawCallback.push({fn:function(t){if(d)for(var e=t._iDisplayStart,n=t._iDisplayLength,a=t.fnRecordsDisplay(),r=-1===n,o=r?0:Math.ceil(e/n),i=r?1:Math.ceil(a/n),l=f(o,i),s=0,u=h.p.length;s<u;s++)ve(t,"pageButton")(t,h.p[s],s,l,o,i);else f.fnUpdate(t,c)},sName:"pagination"})),e}function Yt(t,e,n){var a=t._iDisplayStart,r=t._iDisplayLength,o=t.fnRecordsDisplay(),o=(0===o||-1===r?a=0:"number"==typeof e?o<(a=e*r)&&(a=0):"first"==e?a=0:"previous"==e?(a=0<=r?a-r:0)<0&&(a=0):"next"==e?a+r<o&&(a+=r):"last"==e?a=Math.floor((o-1)/r)*r:W(t,0,"Unknown paging action: "+e,5),t._iDisplayStart!==a);return t._iDisplayStart=a,o?(R(t,null,"page",[t]),n&&v(t)):R(t,null,"page-nc",[t]),o}function Zt(t){return P("<div/>",{id:t.aanFeatures.r?null:t.sTableId+"_processing",class:t.oClasses.sProcessing,role:"status"}).html(t.oLanguage.sProcessing).append("<div><div></div><div></div><div></div><div></div></div>").insertBefore(t.nTable)[0]}function D(t,e){t.oFeatures.bProcessing&&P(t.aanFeatures.r).css("display",e?"block":"none"),R(t,null,"processing",[t,e])}function Kt(t){var e,n,a,r,o,i,l,s,u,c,f,d,h=P(t.nTable),p=t.oScroll;return""===p.sX&&""===p.sY?t.nTable:(e=p.sX,n=p.sY,a=t.oClasses,o=(r=h.children("caption")).length?r[0]._captionSide:null,s=P(h[0].cloneNode(!1)),i=P(h[0].cloneNode(!1)),u=function(t){return t?M(t):null},(l=h.children("tfoot")).length||(l=null),s=P(f="<div/>",{class:a.sScrollWrapper}).append(P(f,{class:a.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:e?u(e):"100%"}).append(P(f,{class:a.sScrollHeadInner}).css({"box-sizing":"content-box",width:p.sXInner||"100%"}).append(s.removeAttr("id").css("margin-left",0).append("top"===o?r:null).append(h.children("thead"))))).append(P(f,{class:a.sScrollBody}).css({position:"relative",overflow:"auto",width:u(e)}).append(h)),l&&s.append(P(f,{class:a.sScrollFoot}).css({overflow:"hidden",border:0,width:e?u(e):"100%"}).append(P(f,{class:a.sScrollFootInner}).append(i.removeAttr("id").css("margin-left",0).append("bottom"===o?r:null).append(h.children("tfoot"))))),u=s.children(),c=u[0],f=u[1],d=l?u[2]:null,e&&P(f).on("scroll.DT",function(t){var e=this.scrollLeft;c.scrollLeft=e,l&&(d.scrollLeft=e)}),P(f).css("max-height",n),p.bCollapse||P(f).css("height",n),t.nScrollHead=c,t.nScrollBody=f,t.nScrollFoot=d,t.aoDrawCallback.push({fn:Qt,sName:"scrolling"}),s[0])}function Qt(n){function t(t){(t=t.style).paddingTop="0",t.paddingBottom="0",t.borderTopWidth="0",t.borderBottomWidth="0",t.height=0}var e,a,r,o,i,l=n.oScroll,s=l.sX,u=l.sXInner,c=l.sY,l=l.iBarWidth,f=P(n.nScrollHead),d=f[0].style,h=f.children("div"),p=h[0].style,h=h.children("table"),g=n.nScrollBody,b=P(g),m=g.style,S=P(n.nScrollFoot).children("div"),v=S.children("table"),y=P(n.nTHead),D=P(n.nTable),_=D[0],w=_.style,C=n.nTFoot?P(n.nTFoot):null,T=n.oBrowser,x=T.bScrollOversize,A=(H(n.aoColumns,"nTh"),[]),I=[],F=[],L=[],R=g.scrollHeight>g.clientHeight;n.scrollBarVis!==R&&n.scrollBarVis!==N?(n.scrollBarVis=R,O(n)):(n.scrollBarVis=R,D.children("thead, tfoot").remove(),C&&(R=C.clone().prependTo(D),i=C.find("tr"),a=R.find("tr"),R.find("[id]").removeAttr("id")),R=y.clone().prependTo(D),y=y.find("tr"),e=R.find("tr"),R.find("th, td").removeAttr("tabindex"),R.find("[id]").removeAttr("id"),s||(m.width="100%",f[0].style.width="100%"),P.each(Ct(n,R),function(t,e){r=rt(n,t),e.style.width=n.aoColumns[r].sWidth}),C&&k(function(t){t.style.width=""},a),f=D.outerWidth(),""===s?(w.width="100%",x&&(D.find("tbody").height()>g.offsetHeight||"scroll"==b.css("overflow-y"))&&(w.width=M(D.outerWidth()-l)),f=D.outerWidth()):""!==u&&(w.width=M(u),f=D.outerWidth()),k(t,e),k(function(t){var e=j.getComputedStyle?j.getComputedStyle(t).width:M(P(t).width());F.push(t.innerHTML),A.push(e)},e),k(function(t,e){t.style.width=A[e]},y),P(e).css("height",0),C&&(k(t,a),k(function(t){L.push(t.innerHTML),I.push(M(P(t).css("width")))},a),k(function(t,e){t.style.width=I[e]},i),P(a).height(0)),k(function(t,e){t.innerHTML='<div class="dataTables_sizing">'+F[e]+"</div>",t.childNodes[0].style.height="0",t.childNodes[0].style.overflow="hidden",t.style.width=A[e]},e),C&&k(function(t,e){t.innerHTML='<div class="dataTables_sizing">'+L[e]+"</div>",t.childNodes[0].style.height="0",t.childNodes[0].style.overflow="hidden",t.style.width=I[e]},a),Math.round(D.outerWidth())<Math.round(f)?(o=g.scrollHeight>g.offsetHeight||"scroll"==b.css("overflow-y")?f+l:f,x&&(g.scrollHeight>g.offsetHeight||"scroll"==b.css("overflow-y"))&&(w.width=M(o-l)),""!==s&&""===u||W(n,1,"Possible column misalignment",6)):o="100%",m.width=M(o),d.width=M(o),C&&(n.nScrollFoot.style.width=M(o)),c||x&&(m.height=M(_.offsetHeight+l)),R=D.outerWidth(),h[0].style.width=M(R),p.width=M(R),y=D.height()>g.clientHeight||"scroll"==b.css("overflow-y"),p[i="padding"+(T.bScrollbarLeft?"Left":"Right")]=y?l+"px":"0px",C&&(v[0].style.width=M(R),S[0].style.width=M(R),S[0].style[i]=y?l+"px":"0px"),D.children("colgroup").insertBefore(D.children("thead")),b.trigger("scroll"),!n.bSorted&&!n.bFiltered||n._drawHold||(g.scrollTop=0))}function k(t,e,n){for(var a,r,o=0,i=0,l=e.length;i<l;){for(a=e[i].firstChild,r=n?n[i].firstChild:null;a;)1===a.nodeType&&(n?t(a,r,o):t(a,o),o++),a=a.nextSibling,r=n?r.nextSibling:null;i++}}var te=/<.*?>/g;function ee(t){var e,n,a=t.nTable,r=t.aoColumns,o=t.oScroll,i=o.sY,l=o.sX,o=o.sXInner,s=r.length,u=it(t,"bVisible"),c=P("th",t.nTHead),f=a.getAttribute("width"),d=a.parentNode,h=!1,p=t.oBrowser,g=p.bScrollOversize,b=a.style.width;for(b&&-1!==b.indexOf("%")&&(f=b),D=0;D<u.length;D++)null!==(e=r[u[D]]).sWidth&&(e.sWidth=ae(e.sWidthOrig,d),h=!0);if(g||!h&&!l&&!i&&s==T(t)&&s==c.length)for(D=0;D<s;D++){var m=rt(t,D);null!==m&&(r[m].sWidth=M(c.eq(D).width()))}else{var b=P(a).clone().css("visibility","hidden").removeAttr("id"),S=(b.find("tbody tr").remove(),P("<tr/>").appendTo(b.find("tbody")));for(b.find("thead, tfoot").remove(),b.append(P(t.nTHead).clone()).append(P(t.nTFoot).clone()),b.find("tfoot th, tfoot td").css("width",""),c=Ct(t,b.find("thead")[0]),D=0;D<u.length;D++)e=r[u[D]],c[D].style.width=null!==e.sWidthOrig&&""!==e.sWidthOrig?M(e.sWidthOrig):"",e.sWidthOrig&&l&&P(c[D]).append(P("<div/>").css({width:e.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(t.aoData.length)for(D=0;D<u.length;D++)e=r[n=u[D]],P(re(t,n)).clone(!1).append(e.sContentPadding).appendTo(S);P("[name]",b).removeAttr("name");for(var v=P("<div/>").css(l||i?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(b).appendTo(d),y=(l&&o?b.width(o):l?(b.css("width","auto"),b.removeAttr("width"),b.width()<d.clientWidth&&f&&b.width(d.clientWidth)):i?b.width(d.clientWidth):f&&b.width(f),0),D=0;D<u.length;D++){var _=P(c[D]),w=_.outerWidth()-_.width(),_=p.bBounding?Math.ceil(c[D].getBoundingClientRect().width):_.outerWidth();y+=_,r[u[D]].sWidth=M(_-w)}a.style.width=M(y),v.remove()}f&&(a.style.width=M(f)),!f&&!l||t._reszEvt||(o=function(){P(j).on("resize.DT-"+t.sInstance,ne(function(){O(t)}))},g?setTimeout(o,1e3):o(),t._reszEvt=!0)}var ne=w.util.throttle;function ae(t,e){return t?(e=(t=P("<div/>").css("width",M(t)).appendTo(e||y.body))[0].offsetWidth,t.remove(),e):0}function re(t,e){var n,a=oe(t,e);return a<0?null:(n=t.aoData[a]).nTr?n.anCells[e]:P("<td/>").html(S(t,a,e,"display"))[0]}function oe(t,e){for(var n,a=-1,r=-1,o=0,i=t.aoData.length;o<i;o++)(n=(n=(n=S(t,o,e,"display")+"").replace(te,"")).replace(/ /g," ")).length>a&&(a=n.length,r=o);return r}function M(t){return null===t?"0px":"number"==typeof t?t<0?"0px":t+"px":t.match(/\d$/)?t+"px":t}function I(t){function e(t){t.length&&!Array.isArray(t[0])?h.push(t):P.merge(h,t)}var n,a,r,o,i,l,s,u=[],c=t.aoColumns,f=t.aaSortingFixed,d=P.isPlainObject(f),h=[];for(Array.isArray(f)&&e(f),d&&f.pre&&e(f.pre),e(t.aaSorting),d&&f.post&&e(f.post),n=0;n<h.length;n++)for(r=(o=c[s=h[n][a=0]].aDataSort).length;a<r;a++)l=c[i=o[a]].sType||"string",h[n]._idx===N&&(h[n]._idx=P.inArray(h[n][1],c[i].asSorting)),u.push({src:s,col:i,dir:h[n][1],index:h[n]._idx,type:l,formatter:w.ext.type.order[l+"-pre"]});return u}function ie(t){var e,n,a,r,c,f=[],u=w.ext.type.order,d=t.aoData,o=(t.aoColumns,0),i=t.aiDisplayMaster;for(lt(t),e=0,n=(c=I(t)).length;e<n;e++)(r=c[e]).formatter&&o++,fe(t,r.col);if("ssp"!=E(t)&&0!==c.length){for(e=0,a=i.length;e<a;e++)f[i[e]]=e;o===c.length?i.sort(function(t,e){for(var n,a,r,o,i=c.length,l=d[t]._aSortData,s=d[e]._aSortData,u=0;u<i;u++)if(0!=(r=(n=l[(o=c[u]).col])<(a=s[o.col])?-1:a<n?1:0))return"asc"===o.dir?r:-r;return(n=f[t])<(a=f[e])?-1:a<n?1:0}):i.sort(function(t,e){for(var n,a,r,o=c.length,i=d[t]._aSortData,l=d[e]._aSortData,s=0;s<o;s++)if(n=i[(r=c[s]).col],a=l[r.col],0!==(r=(u[r.type+"-"+r.dir]||u["string-"+r.dir])(n,a)))return r;return(n=f[t])<(a=f[e])?-1:a<n?1:0})}t.bSorted=!0}function le(t){for(var e=t.aoColumns,n=I(t),a=t.oLanguage.oAria,r=0,o=e.length;r<o;r++){var i=e[r],l=i.asSorting,s=i.ariaTitle||i.sTitle.replace(/<.*?>/g,""),u=i.nTh;u.removeAttribute("aria-sort"),i=i.bSortable?s+("asc"===(0<n.length&&n[0].col==r&&(u.setAttribute("aria-sort","asc"==n[0].dir?"ascending":"descending"),l[n[0].index+1])||l[0])?a.sSortAscending:a.sSortDescending):s,u.setAttribute("aria-label",i)}}function se(t,e,n,a){function r(t,e){var n=t._idx;return(n=n===N?P.inArray(t[1],s):n)+1<s.length?n+1:e?null:0}var o,i=t.aoColumns[e],l=t.aaSorting,s=i.asSorting;"number"==typeof l[0]&&(l=t.aaSorting=[l]),n&&t.oFeatures.bSortMulti?-1!==(i=P.inArray(e,H(l,"0")))?null===(o=null===(o=r(l[i],!0))&&1===l.length?0:o)?l.splice(i,1):(l[i][1]=s[o],l[i]._idx=o):(l.push([e,s[0],0]),l[l.length-1]._idx=0):l.length&&l[0][0]==e?(o=r(l[0]),l.length=1,l[0][1]=s[o],l[0]._idx=o):(l.length=0,l.push([e,s[0]]),l[0]._idx=0),u(t),"function"==typeof a&&a(t)}function ue(e,t,n,a){var r=e.aoColumns[n];me(t,{},function(t){!1!==r.bSortable&&(e.oFeatures.bProcessing?(D(e,!0),setTimeout(function(){se(e,n,t.shiftKey,a),"ssp"!==E(e)&&D(e,!1)},0)):se(e,n,t.shiftKey,a))})}function ce(t){var e,n,a,r=t.aLastSort,o=t.oClasses.sSortColumn,i=I(t),l=t.oFeatures;if(l.bSort&&l.bSortClasses){for(e=0,n=r.length;e<n;e++)a=r[e].src,P(H(t.aoData,"anCells",a)).removeClass(o+(e<2?e+1:3));for(e=0,n=i.length;e<n;e++)a=i[e].src,P(H(t.aoData,"anCells",a)).addClass(o+(e<2?e+1:3))}t.aLastSort=i}function fe(t,e){for(var n,a,r,o=t.aoColumns[e],i=w.ext.order[o.sSortDataType],l=(i&&(n=i.call(t.oInstance,t,e,ot(t,e))),w.ext.type.order[o.sType+"-pre"]),s=0,u=t.aoData.length;s<u;s++)(a=t.aoData[s])._aSortData||(a._aSortData=[]),a._aSortData[e]&&!i||(r=i?n[s]:S(t,s,e,"sort"),a._aSortData[e]=l?l(r):r)}function de(n){var t;n._bLoadingState||(t={time:+new Date,start:n._iDisplayStart,length:n._iDisplayLength,order:P.extend(!0,[],n.aaSorting),search:Et(n.oPreviousSearch),columns:P.map(n.aoColumns,function(t,e){return{visible:t.bVisible,search:Et(n.aoPreSearchCols[e])}})},n.oSavedState=t,R(n,"aoStateSaveParams","stateSaveParams",[n,t]),n.oFeatures.bStateSave&&!n.bDestroying&&n.fnStateSaveCallback.call(n.oInstance,n,t))}function he(e,t,n){var a;if(e.oFeatures.bStateSave)return(a=e.fnStateLoadCallback.call(e.oInstance,e,function(t){pe(e,t,n)}))!==N&&pe(e,a,n),!0;n()}function pe(n,t,e){var a,r,o=n.aoColumns,i=(n._bLoadingState=!0,n._bInitComplete?new w.Api(n):null);if(t&&t.time){var l=R(n,"aoStateLoadParams","stateLoadParams",[n,t]);if(-1!==P.inArray(!1,l))n._bLoadingState=!1;else{l=n.iStateDuration;if(0<l&&t.time<+new Date-1e3*l)n._bLoadingState=!1;else if(t.columns&&o.length!==t.columns.length)n._bLoadingState=!1;else{if(n.oLoadedState=P.extend(!0,{},t),t.length!==N&&(i?i.page.len(t.length):n._iDisplayLength=t.length),t.start!==N&&(null===i?(n._iDisplayStart=t.start,n.iInitDisplayStart=t.start):Yt(n,t.start/n._iDisplayLength)),t.order!==N&&(n.aaSorting=[],P.each(t.order,function(t,e){n.aaSorting.push(e[0]>=o.length?[0,e[1]]:e)})),t.search!==N&&P.extend(n.oPreviousSearch,Bt(t.search)),t.columns){for(a=0,r=t.columns.length;a<r;a++){var s=t.columns[a];s.visible!==N&&(i?i.column(a).visible(s.visible,!1):o[a].bVisible=s.visible),s.search!==N&&P.extend(n.aoPreSearchCols[a],Bt(s.search))}i&&i.columns.adjust()}n._bLoadingState=!1,R(n,"aoStateLoaded","stateLoaded",[n,t])}}}else n._bLoadingState=!1;e()}function ge(t){var e=w.settings,t=P.inArray(t,H(e,"nTable"));return-1!==t?e[t]:null}function W(t,e,n,a){if(n="DataTables warning: "+(t?"table id="+t.sTableId+" - ":"")+n,a&&(n+=". For more information about this error, please see http://datatables.net/tn/"+a),e)j.console&&console.log&&console.log(n);else{e=w.ext,e=e.sErrMode||e.errMode;if(t&&R(t,null,"error",[t,a,n]),"alert"==e)alert(n);else{if("throw"==e)throw new Error(n);"function"==typeof e&&e(t,a,n)}}}function F(n,a,t,e){Array.isArray(t)?P.each(t,function(t,e){Array.isArray(e)?F(n,a,e[0],e[1]):F(n,a,e)}):(e===N&&(e=t),a[t]!==N&&(n[e]=a[t]))}function be(t,e,n){var a,r;for(r in e)e.hasOwnProperty(r)&&(a=e[r],P.isPlainObject(a)?(P.isPlainObject(t[r])||(t[r]={}),P.extend(!0,t[r],a)):n&&"data"!==r&&"aaData"!==r&&Array.isArray(a)?t[r]=a.slice():t[r]=a);return t}function me(e,t,n){P(e).on("click.DT",t,function(t){P(e).trigger("blur"),n(t)}).on("keypress.DT",t,function(t){13===t.which&&(t.preventDefault(),n(t))}).on("selectstart.DT",function(){return!1})}function L(t,e,n,a){n&&t[e].push({fn:n,sName:a})}function R(n,t,e,a){var r=[];return t&&(r=P.map(n[t].slice().reverse(),function(t,e){return t.fn.apply(n.oInstance,a)})),null!==e&&(t=P.Event(e+".dt"),(e=P(n.nTable)).trigger(t,a),0===e.parents("body").length&&P("body").trigger(t,a),r.push(t.result)),r}function Se(t){var e=t._iDisplayStart,n=t.fnDisplayEnd(),a=t._iDisplayLength;n<=e&&(e=n-a),e-=e%a,t._iDisplayStart=e=-1===a||e<0?0:e}function ve(t,e){var t=t.renderer,n=w.ext.renderer[e];return P.isPlainObject(t)&&t[e]?n[t[e]]||n._:"string"==typeof t&&n[t]||n._}function E(t){return t.oFeatures.bServerSide?"ssp":t.ajax||t.sAjaxSource?"ajax":"dom"}function ye(t,n){var a;return Array.isArray(t)?P.map(t,function(t){return ye(t,n)}):"number"==typeof t?[n[t]]:(a=P.map(n,function(t,e){return t.nTable}),P(a).filter(t).map(function(t){var e=P.inArray(this,a);return n[e]}).toArray())}function De(r,o,t){var e,n;t&&(e=new B(r)).one("draw",function(){t(e.ajax.json())}),"ssp"==E(r)?u(r,o):(D(r,!0),(n=r.jqXHR)&&4!==n.readyState&&n.abort(),Tt(r,[],function(t){pt(r);for(var e=Ft(r,t),n=0,a=e.length;n<a;n++)x(r,e[n]);u(r,o),D(r,!1)}))}function _e(t,e,n,a,r){for(var o,i,l,s,u=[],c=typeof e,f=0,d=(e=e&&"string"!=c&&"function"!=c&&e.length!==N?e:[e]).length;f<d;f++)for(l=0,s=(i=e[f]&&e[f].split&&!e[f].match(/[\[\(:]/)?e[f].split(","):[e[f]]).length;l<s;l++)(o=n("string"==typeof i[l]?i[l].trim():i[l]))&&o.length&&(u=u.concat(o));var h=p.selector[t];if(h.length)for(f=0,d=h.length;f<d;f++)u=h[f](a,r,u);return z(u)}function we(t){return(t=t||{}).filter&&t.search===N&&(t.search=t.filter),P.extend({search:"none",order:"current",page:"all"},t)}function Ce(t){for(var e=0,n=t.length;e<n;e++)if(0<t[e].length)return t[0]=t[e],t[0].length=1,t.length=1,t.context=[t.context[e]],t;return t.length=0,t}function Te(o,t,e,n){function i(t,e){var n;if(Array.isArray(t)||t instanceof P)for(var a=0,r=t.length;a<r;a++)i(t[a],e);else t.nodeName&&"tr"===t.nodeName.toLowerCase()?l.push(t):(n=P("<tr><td></td></tr>").addClass(e),P("td",n).addClass(e).html(t)[0].colSpan=T(o),l.push(n[0]))}var l=[];i(e,n),t._details&&t._details.detach(),t._details=P(l),t._detailsShow&&t._details.insertAfter(t.nTr)}function xe(t,e){var n=t.context;if(n.length&&t.length){var a=n[0].aoData[t[0]];if(a._details){(a._detailsShow=e)?(a._details.insertAfter(a.nTr),P(a.nTr).addClass("dt-hasChild")):(a._details.detach(),P(a.nTr).removeClass("dt-hasChild")),R(n[0],null,"childRow",[e,t.row(t[0])]);var s=n[0],r=new B(s),a=".dt.DT_details",e="draw"+a,t="column-sizing"+a,a="destroy"+a,u=s.aoData;if(r.off(e+" "+t+" "+a),H(u,"_details").length>0){r.on(e,function(t,e){if(s!==e)return;r.rows({page:"current"}).eq(0).each(function(t){var e=u[t];if(e._detailsShow)e._details.insertAfter(e.nTr)})});r.on(t,function(t,e,n,a){if(s!==e)return;var r,o=T(e);for(var i=0,l=u.length;i<l;i++){r=u[i];if(r._details)r._details.children("td[colspan]").attr("colspan",o)}});r.on(a,function(t,e){if(s!==e)return;for(var n=0,a=u.length;n<a;n++)if(u[n]._details)Re(r,n)})}Le(n)}}}function Ae(t,e,n,a,r){for(var o=[],i=0,l=r.length;i<l;i++)o.push(S(t,r[i],e));return o}var Ie=[],o=Array.prototype,B=function(t,e){if(!(this instanceof B))return new B(t,e);function n(t){var e,n,a,r;t=t,a=w.settings,r=P.map(a,function(t,e){return t.nTable}),(t=t?t.nTable&&t.oApi?[t]:t.nodeName&&"table"===t.nodeName.toLowerCase()?-1!==(e=P.inArray(t,r))?[a[e]]:null:t&&"function"==typeof t.settings?t.settings().toArray():("string"==typeof t?n=P(t):t instanceof P&&(n=t),n?n.map(function(t){return-1!==(e=P.inArray(this,r))?a[e]:null}).toArray():void 0):[])&&o.push.apply(o,t)}var o=[];if(Array.isArray(t))for(var a=0,r=t.length;a<r;a++)n(t[a]);else n(t);this.context=z(o),e&&P.merge(this,e),this.selector={rows:null,cols:null,opts:null},B.extend(this,this,Ie)},Fe=(w.Api=B,P.extend(B.prototype,{any:function(){return 0!==this.count()},concat:o.concat,context:[],count:function(){return this.flatten().length},each:function(t){for(var e=0,n=this.length;e<n;e++)t.call(this,this[e],e,this);return this},eq:function(t){var e=this.context;return e.length>t?new B(e[t],this[t]):null},filter:function(t){var e=[];if(o.filter)e=o.filter.call(this,t,this);else for(var n=0,a=this.length;n<a;n++)t.call(this,this[n],n,this)&&e.push(this[n]);return new B(this.context,e)},flatten:function(){var t=[];return new B(this.context,t.concat.apply(t,this.toArray()))},join:o.join,indexOf:o.indexOf||function(t,e){for(var n=e||0,a=this.length;n<a;n++)if(this[n]===t)return n;return-1},iterator:function(t,e,n,a){var r,o,i,l,s,u,c,f,d=[],h=this.context,p=this.selector;for("string"==typeof t&&(a=n,n=e,e=t,t=!1),o=0,i=h.length;o<i;o++){var g=new B(h[o]);if("table"===e)(r=n.call(g,h[o],o))!==N&&d.push(r);else if("columns"===e||"rows"===e)(r=n.call(g,h[o],this[o],o))!==N&&d.push(r);else if("column"===e||"column-rows"===e||"row"===e||"cell"===e)for(c=this[o],"column-rows"===e&&(u=Fe(h[o],p.opts)),l=0,s=c.length;l<s;l++)f=c[l],(r="cell"===e?n.call(g,h[o],f.row,f.column,o,l):n.call(g,h[o],f,o,l,u))!==N&&d.push(r)}return d.length||a?((t=(a=new B(h,t?d.concat.apply([],d):d)).selector).rows=p.rows,t.cols=p.cols,t.opts=p.opts,a):this},lastIndexOf:o.lastIndexOf||function(t,e){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(t){var e=[];if(o.map)e=o.map.call(this,t,this);else for(var n=0,a=this.length;n<a;n++)e.push(t.call(this,this[n],n));return new B(this.context,e)},pluck:function(t){var e=w.util.get(t);return this.map(function(t){return e(t)})},pop:o.pop,push:o.push,reduce:o.reduce||function(t,e){return et(this,t,e,0,this.length,1)},reduceRight:o.reduceRight||function(t,e){return et(this,t,e,this.length-1,-1,-1)},reverse:o.reverse,selector:null,shift:o.shift,slice:function(){return new B(this.context,this)},sort:o.sort,splice:o.splice,toArray:function(){return o.slice.call(this)},to$:function(){return P(this)},toJQuery:function(){return P(this)},unique:function(){return new B(this.context,z(this))},unshift:o.unshift}),B.extend=function(t,e,n){if(n.length&&e&&(e instanceof B||e.__dt_wrapper))for(var a,r=0,o=n.length;r<o;r++)e[(a=n[r]).name]="function"===a.type?function(e,n,a){return function(){var t=n.apply(e,arguments);return B.extend(t,t,a.methodExt),t}}(t,a.val,a):"object"===a.type?{}:a.val,e[a.name].__dt_wrapper=!0,B.extend(t,e[a.name],a.propExt)},B.register=e=function(t,e){if(Array.isArray(t))for(var n=0,a=t.length;n<a;n++)B.register(t[n],e);else for(var r=t.split("."),o=Ie,i=0,l=r.length;i<l;i++){var s,u,c=function(t,e){for(var n=0,a=t.length;n<a;n++)if(t[n].name===e)return t[n];return null}(o,u=(s=-1!==r[i].indexOf("()"))?r[i].replace("()",""):r[i]);c||o.push(c={name:u,val:{},methodExt:[],propExt:[],type:"object"}),i===l-1?(c.val=e,c.type="function"==typeof e?"function":P.isPlainObject(e)?"object":"other"):o=s?c.methodExt:c.propExt}},B.registerPlural=t=function(t,e,n){B.register(t,n),B.register(e,function(){var t=n.apply(this,arguments);return t===this?this:t instanceof B?t.length?Array.isArray(t[0])?new B(t.context,t[0]):t[0]:N:t})},e("tables()",function(t){return t!==N&&null!==t?new B(ye(t,this.context)):this}),e("table()",function(t){var t=this.tables(t),e=t.context;return e.length?new B(e[0]):t}),t("tables().nodes()","table().node()",function(){return this.iterator("table",function(t){return t.nTable},1)}),t("tables().body()","table().body()",function(){return this.iterator("table",function(t){return t.nTBody},1)}),t("tables().header()","table().header()",function(){return this.iterator("table",function(t){return t.nTHead},1)}),t("tables().footer()","table().footer()",function(){return this.iterator("table",function(t){return t.nTFoot},1)}),t("tables().containers()","table().container()",function(){return this.iterator("table",function(t){return t.nTableWrapper},1)}),e("draw()",function(e){return this.iterator("table",function(t){"page"===e?v(t):u(t,!1===(e="string"==typeof e?"full-hold"!==e:e))})}),e("page()",function(e){return e===N?this.page.info().page:this.iterator("table",function(t){Yt(t,e)})}),e("page.info()",function(t){var e,n,a,r,o;return 0===this.context.length?N:(n=(e=this.context[0])._iDisplayStart,a=e.oFeatures.bPaginate?e._iDisplayLength:-1,r=e.fnRecordsDisplay(),{page:(o=-1===a)?0:Math.floor(n/a),pages:o?1:Math.ceil(r/a),start:n,end:e.fnDisplayEnd(),length:a,recordsTotal:e.fnRecordsTotal(),recordsDisplay:r,serverSide:"ssp"===E(e)})}),e("page.len()",function(e){return e===N?0!==this.context.length?this.context[0]._iDisplayLength:N:this.iterator("table",function(t){Gt(t,e)})}),e("ajax.json()",function(){var t=this.context;if(0<t.length)return t[0].json}),e("ajax.params()",function(){var t=this.context;if(0<t.length)return t[0].oAjaxData}),e("ajax.reload()",function(e,n){return this.iterator("table",function(t){De(t,!1===n,e)})}),e("ajax.url()",function(e){var t=this.context;return e===N?0===t.length?N:(t=t[0]).ajax?P.isPlainObject(t.ajax)?t.ajax.url:t.ajax:t.sAjaxSource:this.iterator("table",function(t){P.isPlainObject(t.ajax)?t.ajax.url=e:t.ajax=e})}),e("ajax.url().load()",function(e,n){return this.iterator("table",function(t){De(t,!1===n,e)})}),function(t,e){var n,a=[],r=t.aiDisplay,o=t.aiDisplayMaster,i=e.search,l=e.order,e=e.page;if("ssp"==E(t))return"removed"===i?[]:f(0,o.length);if("current"==e)for(u=t._iDisplayStart,c=t.fnDisplayEnd();u<c;u++)a.push(r[u]);else if("current"==l||"applied"==l){if("none"==i)a=o.slice();else if("applied"==i)a=r.slice();else if("removed"==i){for(var s={},u=0,c=r.length;u<c;u++)s[r[u]]=null;a=P.map(o,function(t){return s.hasOwnProperty(t)?null:t})}}else if("index"==l||"original"==l)for(u=0,c=t.aoData.length;u<c;u++)("none"==i||-1===(n=P.inArray(u,r))&&"removed"==i||0<=n&&"applied"==i)&&a.push(u);return a}),Le=(e("rows()",function(e,n){e===N?e="":P.isPlainObject(e)&&(n=e,e=""),n=we(n);var t=this.iterator("table",function(t){return _e("row",e,function(n){var t=d(n),a=r.aoData;if(null!==t&&!o)return[t];if(i=i||Fe(r,o),null!==t&&-1!==P.inArray(t,i))return[t];if(null===n||n===N||""===n)return i;if("function"==typeof n)return P.map(i,function(t){var e=a[t];return n(t,e._aData,e.nTr)?t:null});if(n.nodeName)return t=n._DT_RowIndex,e=n._DT_CellIndex,t!==N?a[t]&&a[t].nTr===n?[t]:[]:e?a[e.row]&&a[e.row].nTr===n.parentNode?[e.row]:[]:(t=P(n).closest("*[data-dt-row]")).length?[t.data("dt-row")]:[];if("string"==typeof n&&"#"===n.charAt(0)){var e=r.aIds[n.replace(/^#/,"")];if(e!==N)return[e.idx]}t=_(m(r.aoData,i,"nTr"));return P(t).filter(n).map(function(){return this._DT_RowIndex}).toArray()},r=t,o=n);var r,o,i},1);return t.selector.rows=e,t.selector.opts=n,t}),e("rows().nodes()",function(){return this.iterator("row",function(t,e){return t.aoData[e].nTr||N},1)}),e("rows().data()",function(){return this.iterator(!0,"rows",function(t,e){return m(t.aoData,e,"_aData")},1)}),t("rows().cache()","row().cache()",function(n){return this.iterator("row",function(t,e){t=t.aoData[e];return"search"===n?t._aFilterData:t._aSortData},1)}),t("rows().invalidate()","row().invalidate()",function(n){return this.iterator("row",function(t,e){bt(t,e,n)})}),t("rows().indexes()","row().index()",function(){return this.iterator("row",function(t,e){return e},1)}),t("rows().ids()","row().id()",function(t){for(var e=[],n=this.context,a=0,r=n.length;a<r;a++)for(var o=0,i=this[a].length;o<i;o++){var l=n[a].rowIdFn(n[a].aoData[this[a][o]]._aData);e.push((!0===t?"#":"")+l)}return new B(n,e)}),t("rows().remove()","row().remove()",function(){var f=this;return this.iterator("row",function(t,e,n){var a,r,o,i,l,s,u=t.aoData,c=u[e];for(u.splice(e,1),a=0,r=u.length;a<r;a++)if(s=(l=u[a]).anCells,null!==l.nTr&&(l.nTr._DT_RowIndex=a),null!==s)for(o=0,i=s.length;o<i;o++)s[o]._DT_CellIndex.row=a;gt(t.aiDisplayMaster,e),gt(t.aiDisplay,e),gt(f[n],e,!1),0<t._iRecordsDisplay&&t._iRecordsDisplay--,Se(t);n=t.rowIdFn(c._aData);n!==N&&delete t.aIds[n]}),this.iterator("table",function(t){for(var e=0,n=t.aoData.length;e<n;e++)t.aoData[e].idx=e}),this}),e("rows.add()",function(o){var t=this.iterator("table",function(t){for(var e,n=[],a=0,r=o.length;a<r;a++)(e=o[a]).nodeName&&"TR"===e.nodeName.toUpperCase()?n.push(ut(t,e)[0]):n.push(x(t,e));return n},1),e=this.rows(-1);return e.pop(),P.merge(e,t),e}),e("row()",function(t,e){return Ce(this.rows(t,e))}),e("row().data()",function(t){var e,n=this.context;return t===N?n.length&&this.length?n[0].aoData[this[0]]._aData:N:((e=n[0].aoData[this[0]])._aData=t,Array.isArray(t)&&e.nTr&&e.nTr.id&&b(n[0].rowId)(t,e.nTr.id),bt(n[0],this[0],"data"),this)}),e("row().node()",function(){var t=this.context;return t.length&&this.length&&t[0].aoData[this[0]].nTr||null}),e("row.add()",function(e){e instanceof P&&e.length&&(e=e[0]);var t=this.iterator("table",function(t){return e.nodeName&&"TR"===e.nodeName.toUpperCase()?ut(t,e)[0]:x(t,e)});return this.row(t[0])}),P(y).on("plugin-init.dt",function(t,e){var n=new B(e),a="on-plugin-init",r="stateSaveParams."+a,o="destroy. "+a,a=(n.on(r,function(t,e,n){for(var a=e.rowIdFn,r=e.aoData,o=[],i=0;i<r.length;i++)r[i]._detailsShow&&o.push("#"+a(r[i]._aData));n.childRows=o}),n.on(o,function(){n.off(r+" "+o)}),n.state.loaded());a&&a.childRows&&n.rows(P.map(a.childRows,function(t){return t.replace(/:/g,"\\:")})).every(function(){R(e,null,"requestChild",[this])})}),w.util.throttle(function(t){de(t[0])},500)),Re=function(t,e){var n=t.context;n.length&&(e=n[0].aoData[e!==N?e:t[0]])&&e._details&&(e._details.remove(),e._detailsShow=N,e._details=N,P(e.nTr).removeClass("dt-hasChild"),Le(n))},Pe="row().child",je=Pe+"()",Ne=(e(je,function(t,e){var n=this.context;return t===N?n.length&&this.length?n[0].aoData[this[0]]._details:N:(!0===t?this.child.show():!1===t?Re(this):n.length&&this.length&&Te(n[0],n[0].aoData[this[0]],t,e),this)}),e([Pe+".show()",je+".show()"],function(t){return xe(this,!0),this}),e([Pe+".hide()",je+".hide()"],function(){return xe(this,!1),this}),e([Pe+".remove()",je+".remove()"],function(){return Re(this),this}),e(Pe+".isShown()",function(){var t=this.context;return t.length&&this.length&&t[0].aoData[this[0]]._detailsShow||!1}),/^([^:]+):(name|visIdx|visible)$/),He=(e("columns()",function(n,a){n===N?n="":P.isPlainObject(n)&&(a=n,n=""),a=we(a);var t=this.iterator("table",function(t){return e=n,l=a,s=(i=t).aoColumns,u=H(s,"sName"),c=H(s,"nTh"),_e("column",e,function(n){var a,t=d(n);if(""===n)return f(s.length);if(null!==t)return[0<=t?t:s.length+t];if("function"==typeof n)return a=Fe(i,l),P.map(s,function(t,e){return n(e,Ae(i,e,0,0,a),c[e])?e:null});var r="string"==typeof n?n.match(Ne):"";if(r)switch(r[2]){case"visIdx":case"visible":var e,o=parseInt(r[1],10);return o<0?[(e=P.map(s,function(t,e){return t.bVisible?e:null}))[e.length+o]]:[rt(i,o)];case"name":return P.map(u,function(t,e){return t===r[1]?e:null});default:return[]}return n.nodeName&&n._DT_CellIndex?[n._DT_CellIndex.column]:(t=P(c).filter(n).map(function(){return P.inArray(this,c)}).toArray()).length||!n.nodeName?t:(t=P(n).closest("*[data-dt-column]")).length?[t.data("dt-column")]:[]},i,l);var i,e,l,s,u,c},1);return t.selector.cols=n,t.selector.opts=a,t}),t("columns().header()","column().header()",function(t,e){return this.iterator("column",function(t,e){return t.aoColumns[e].nTh},1)}),t("columns().footer()","column().footer()",function(t,e){return this.iterator("column",function(t,e){return t.aoColumns[e].nTf},1)}),t("columns().data()","column().data()",function(){return this.iterator("column-rows",Ae,1)}),t("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(t,e){return t.aoColumns[e].mData},1)}),t("columns().cache()","column().cache()",function(o){return this.iterator("column-rows",function(t,e,n,a,r){return m(t.aoData,r,"search"===o?"_aFilterData":"_aSortData",e)},1)}),t("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(t,e,n,a,r){return m(t.aoData,r,"anCells",e)},1)}),t("columns().visible()","column().visible()",function(f,n){var e=this,t=this.iterator("column",function(t,e){if(f===N)return t.aoColumns[e].bVisible;var n,a,r=e,e=f,o=t.aoColumns,i=o[r],l=t.aoData;if(e===N)i.bVisible;else if(i.bVisible!==e){if(e)for(var s=P.inArray(!0,H(o,"bVisible"),r+1),u=0,c=l.length;u<c;u++)a=l[u].nTr,n=l[u].anCells,a&&a.insertBefore(n[r],n[s]||null);else P(H(t.aoData,"anCells",r)).detach();i.bVisible=e}});return f!==N&&this.iterator("table",function(t){Dt(t,t.aoHeader),Dt(t,t.aoFooter),t.aiDisplay.length||P(t.nTBody).find("td[colspan]").attr("colspan",T(t)),de(t),e.iterator("column",function(t,e){R(t,null,"column-visibility",[t,e,f,n])}),n!==N&&!n||e.columns.adjust()}),t}),t("columns().indexes()","column().index()",function(n){return this.iterator("column",function(t,e){return"visible"===n?ot(t,e):e},1)}),e("columns.adjust()",function(){return this.iterator("table",function(t){O(t)},1)}),e("column.index()",function(t,e){var n;if(0!==this.context.length)return n=this.context[0],"fromVisible"===t||"toData"===t?rt(n,e):"fromData"===t||"toVisible"===t?ot(n,e):void 0}),e("column()",function(t,e){return Ce(this.columns(t,e))}),e("cells()",function(g,t,b){var a,r,o,i,l,s,e;return P.isPlainObject(g)&&(g.row===N?(b=g,g=null):(b=t,t=null)),P.isPlainObject(t)&&(b=t,t=null),null===t||t===N?this.iterator("table",function(t){return a=t,t=g,e=we(b),f=a.aoData,d=Fe(a,e),n=_(m(f,d,"anCells")),h=P(Y([],n)),p=a.aoColumns.length,_e("cell",t,function(t){var e,n="function"==typeof t;if(null===t||t===N||n){for(o=[],i=0,l=d.length;i<l;i++)for(r=d[i],s=0;s<p;s++)u={row:r,column:s},(!n||(c=f[r],t(u,S(a,r,s),c.anCells?c.anCells[s]:null)))&&o.push(u);return o}return P.isPlainObject(t)?t.column!==N&&t.row!==N&&-1!==P.inArray(t.row,d)?[t]:[]:(e=h.filter(t).map(function(t,e){return{row:e._DT_CellIndex.row,column:e._DT_CellIndex.column}}).toArray()).length||!t.nodeName?e:(c=P(t).closest("*[data-dt-row]")).length?[{row:c.data("dt-row"),column:c.data("dt-column")}]:[]},a,e);var a,e,r,o,i,l,s,u,c,f,d,n,h,p}):(e=b?{page:b.page,order:b.order,search:b.search}:{},a=this.columns(t,e),r=this.rows(g,e),e=this.iterator("table",function(t,e){var n=[];for(o=0,i=r[e].length;o<i;o++)for(l=0,s=a[e].length;l<s;l++)n.push({row:r[e][o],column:a[e][l]});return n},1),e=b&&b.selected?this.cells(e,b):e,P.extend(e.selector,{cols:t,rows:g,opts:b}),e)}),t("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(t,e,n){t=t.aoData[e];return t&&t.anCells?t.anCells[n]:N},1)}),e("cells().data()",function(){return this.iterator("cell",function(t,e,n){return S(t,e,n)},1)}),t("cells().cache()","cell().cache()",function(a){return a="search"===a?"_aFilterData":"_aSortData",this.iterator("cell",function(t,e,n){return t.aoData[e][a][n]},1)}),t("cells().render()","cell().render()",function(a){return this.iterator("cell",function(t,e,n){return S(t,e,n,a)},1)}),t("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(t,e,n){return{row:e,column:n,columnVisible:ot(t,n)}},1)}),t("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(t,e,n){bt(t,e,a,n)})}),e("cell()",function(t,e,n){return Ce(this.cells(t,e,n))}),e("cell().data()",function(t){var e=this.context,n=this[0];return t===N?e.length&&n.length?S(e[0],n[0].row,n[0].column):N:(ct(e[0],n[0].row,n[0].column,t),bt(e[0],n[0].row,"data",n[0].column),this)}),e("order()",function(e,t){var n=this.context;return e===N?0!==n.length?n[0].aaSorting:N:("number"==typeof e?e=[[e,t]]:e.length&&!Array.isArray(e[0])&&(e=Array.prototype.slice.call(arguments)),this.iterator("table",function(t){t.aaSorting=e.slice()}))}),e("order.listener()",function(e,n,a){return this.iterator("table",function(t){ue(t,e,n,a)})}),e("order.fixed()",function(e){var t;return e?this.iterator("table",function(t){t.aaSortingFixed=P.extend(!0,{},e)}):(t=(t=this.context).length?t[0].aaSortingFixed:N,Array.isArray(t)?{pre:t}:t)}),e(["columns().order()","column().order()"],function(a){var r=this;return this.iterator("table",function(t,e){var n=[];P.each(r[e],function(t,e){n.push([e,a])}),t.aaSorting=n})}),e("search()",function(e,n,a,r){var t=this.context;return e===N?0!==t.length?t[0].oPreviousSearch.sSearch:N:this.iterator("table",function(t){t.oFeatures.bFilter&&Rt(t,P.extend({},t.oPreviousSearch,{sSearch:e+"",bRegex:null!==n&&n,bSmart:null===a||a,bCaseInsensitive:null===r||r}),1)})}),t("columns().search()","column().search()",function(a,r,o,i){return this.iterator("column",function(t,e){var n=t.aoPreSearchCols;if(a===N)return n[e].sSearch;t.oFeatures.bFilter&&(P.extend(n[e],{sSearch:a+"",bRegex:null!==r&&r,bSmart:null===o||o,bCaseInsensitive:null===i||i}),Rt(t,t.oPreviousSearch,1))})}),e("state()",function(){return this.context.length?this.context[0].oSavedState:null}),e("state.clear()",function(){return this.iterator("table",function(t){t.fnStateSaveCallback.call(t.oInstance,t,{})})}),e("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null}),e("state.save()",function(){return this.iterator("table",function(t){de(t)})}),w.use=function(t,e){"lib"===e||t.fn?P=t:"win"!=e&&!t.document||(y=(j=t).document)},w.factory=function(t,e){var n=!1;return t&&t.document&&(y=(j=t).document),e&&e.fn&&e.fn.jquery&&(P=e,n=!0),n},w.versionCheck=w.fnVersionCheck=function(t){for(var e,n,a=w.version.split("."),r=t.split("."),o=0,i=r.length;o<i;o++)if((e=parseInt(a[o],10)||0)!==(n=parseInt(r[o],10)||0))return n<e;return!0},w.isDataTable=w.fnIsDataTable=function(t){var r=P(t).get(0),o=!1;return t instanceof w.Api||(P.each(w.settings,function(t,e){var n=e.nScrollHead?P("table",e.nScrollHead)[0]:null,a=e.nScrollFoot?P("table",e.nScrollFoot)[0]:null;e.nTable!==r&&n!==r&&a!==r||(o=!0)}),o)},w.tables=w.fnTables=function(e){var t=!1,n=(P.isPlainObject(e)&&(t=e.api,e=e.visible),P.map(w.settings,function(t){if(!e||P(t.nTable).is(":visible"))return t.nTable}));return t?new B(n):n},w.camelToHungarian=C,e("$()",function(t,e){e=this.rows(e).nodes(),e=P(e);return P([].concat(e.filter(t).toArray(),e.find(t).toArray()))}),P.each(["on","one","off"],function(t,n){e(n+"()",function(){var t=Array.prototype.slice.call(arguments),e=(t[0]=P.map(t[0].split(/\s/),function(t){return t.match(/\.dt\b/)?t:t+".dt"}).join(" "),P(this.tables().nodes()));return e[n].apply(e,t),this})}),e("clear()",function(){return this.iterator("table",function(t){pt(t)})}),e("settings()",function(){return new B(this.context,this.context)}),e("init()",function(){var t=this.context;return t.length?t[0].oInit:null}),e("data()",function(){return this.iterator("table",function(t){return H(t.aoData,"_aData")}).flatten()}),e("destroy()",function(c){return c=c||!1,this.iterator("table",function(e){var n,t=e.oClasses,a=e.nTable,r=e.nTBody,o=e.nTHead,i=e.nTFoot,l=P(a),r=P(r),s=P(e.nTableWrapper),u=P.map(e.aoData,function(t){return t.nTr}),i=(e.bDestroying=!0,R(e,"aoDestroyCallback","destroy",[e]),c||new B(e).columns().visible(!0),s.off(".DT").find(":not(tbody *)").off(".DT"),P(j).off(".DT-"+e.sInstance),a!=o.parentNode&&(l.children("thead").detach(),l.append(o)),i&&a!=i.parentNode&&(l.children("tfoot").detach(),l.append(i)),e.aaSorting=[],e.aaSortingFixed=[],ce(e),P(u).removeClass(e.asStripeClasses.join(" ")),P("th, td",o).removeClass(t.sSortable+" "+t.sSortableAsc+" "+t.sSortableDesc+" "+t.sSortableNone),r.children().detach(),r.append(u),e.nTableWrapper.parentNode),o=c?"remove":"detach",u=(l[o](),s[o](),!c&&i&&(i.insertBefore(a,e.nTableReinsertBefore),l.css("width",e.sDestroyWidth).removeClass(t.sTable),n=e.asDestroyStripes.length)&&r.children().each(function(t){P(this).addClass(e.asDestroyStripes[t%n])}),P.inArray(e,w.settings));-1!==u&&w.settings.splice(u,1)})}),P.each(["column","row","cell"],function(t,s){e(s+"s().every()",function(o){var i=this.selector.opts,l=this;return this.iterator(s,function(t,e,n,a,r){o.call(l[s](e,"cell"===s?n:i,"cell"===s?i:N),e,n,a,r)})})}),e("i18n()",function(t,e,n){var a=this.context[0],t=A(t)(a.oLanguage);return t===N&&(t=e),(t=n!==N&&P.isPlainObject(t)?t[n]!==N?t[n]:t._:t).replace("%d",n)}),w.version="1.13.4",w.settings=[],w.models={},w.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0,return:!1},w.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1},w.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null},w.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(t){return t.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(t){try{return JSON.parse((-1===t.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+t.sInstance+"_"+location.pathname))}catch(t){return{}}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(t,e){try{(-1===t.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+t.sInstance+"_"+location.pathname,JSON.stringify(e))}catch(t){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:P.extend({},w.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"},i(w.defaults),w.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null},i(w.defaults.column),w.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,jqXHR:null,json:N,oAjaxData:N,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==E(this)?+this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==E(this)?+this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var t=this._iDisplayLength,e=this._iDisplayStart,n=e+t,a=this.aiDisplay.length,r=this.oFeatures,o=r.bPaginate;return r.bServerSide?!1===o||-1===t?e+a:Math.min(e+t,this._iRecordsDisplay):!o||a<n||-1===t?a:n},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null},w.ext=p={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:w.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:w.version},P.extend(p,{afnFiltering:p.search,aTypes:p.type.detect,ofnSearch:p.type.search,oSort:p.type.order,afnSortData:p.order,aoFeatures:p.feature,oApi:p.internal,oStdClasses:p.classes,oPagination:p.pager}),P.extend(w.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_desc_disabled",sSortableDesc:"sorting_asc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""}),w.ext.pager);function Oe(t,e){var n=[],a=He.numbers_length,r=Math.floor(a/2);return e<=a?n=f(0,e):t<=r?((n=f(0,a-2)).push("ellipsis"),n.push(e-1)):((e-1-r<=t?n=f(e-(a-2),e):((n=f(t-r+2,t+r-1)).push("ellipsis"),n.push(e-1),n)).splice(0,0,"ellipsis"),n.splice(0,0,0)),n.DT_el="span",n}P.extend(He,{simple:function(t,e){return["previous","next"]},full:function(t,e){return["first","previous","next","last"]},numbers:function(t,e){return[Oe(t,e)]},simple_numbers:function(t,e){return["previous",Oe(t,e),"next"]},full_numbers:function(t,e){return["first","previous",Oe(t,e),"next","last"]},first_last_numbers:function(t,e){return["first",Oe(t,e),"last"]},_numbers:Oe,numbers_length:7}),P.extend(!0,w.ext.renderer,{pageButton:{_:function(c,t,f,e,d,h){function p(t,e){for(var n,a,r,o=m.sPageButtonDisabled,i=function(t){Yt(c,t.data.action,!0)},l=0,s=e.length;l<s;l++)if(n=e[l],Array.isArray(n)){var u=P("<"+(n.DT_el||"div")+"/>").appendTo(t);p(u,n)}else{switch(g=null,b=n,a=c.iTabIndex,n){case"ellipsis":t.append('<span class="ellipsis">…</span>');break;case"first":g=S.sFirst,0===d&&(a=-1,b+=" "+o);break;case"previous":g=S.sPrevious,0===d&&(a=-1,b+=" "+o);break;case"next":g=S.sNext,0!==h&&d!==h-1||(a=-1,b+=" "+o);break;case"last":g=S.sLast,0!==h&&d!==h-1||(a=-1,b+=" "+o);break;default:g=c.fnFormatNumber(n+1),b=d===n?m.sPageButtonActive:""}null!==g&&(u=c.oInit.pagingTag||"a",r=-1!==b.indexOf(o),me(P("<"+u+">",{class:m.sPageButton+" "+b,"aria-controls":c.sTableId,"aria-disabled":r?"true":null,"aria-label":v[n],"aria-role":"link","aria-current":b===m.sPageButtonActive?"page":null,"data-dt-idx":n,tabindex:a,id:0===f&&"string"==typeof n?c.sTableId+"_"+n:null}).html(g).appendTo(t),{action:n},i))}}var g,b,n,m=c.oClasses,S=c.oLanguage.oPaginate,v=c.oLanguage.oAria.paginate||{};try{n=P(t).find(y.activeElement).data("dt-idx")}catch(t){}p(P(t).empty(),e),n!==N&&P(t).find("[data-dt-idx="+n+"]").trigger("focus")}}}),P.extend(w.ext.type.detect,[function(t,e){e=e.oLanguage.sDecimal;return l(t,e)?"num"+e:null},function(t,e){var n;return(!t||t instanceof Date||X.test(t))&&(null!==(n=Date.parse(t))&&!isNaN(n)||h(t))?"date":null},function(t,e){e=e.oLanguage.sDecimal;return l(t,e,!0)?"num-fmt"+e:null},function(t,e){e=e.oLanguage.sDecimal;return a(t,e)?"html-num"+e:null},function(t,e){e=e.oLanguage.sDecimal;return a(t,e,!0)?"html-num-fmt"+e:null},function(t,e){return h(t)||"string"==typeof t&&-1!==t.indexOf("<")?"html":null}]),P.extend(w.ext.type.search,{html:function(t){return h(t)?t:"string"==typeof t?t.replace(U," ").replace(V,""):""},string:function(t){return!h(t)&&"string"==typeof t?t.replace(U," "):t}});function ke(t,e,n,a){var r;return 0===t||t&&"-"!==t?"number"==(r=typeof t)||"bigint"==r?t:+(t=(t=e?G(t,e):t).replace&&(n&&(t=t.replace(n,"")),a)?t.replace(a,""):t):-1/0}function Me(n){P.each({num:function(t){return ke(t,n)},"num-fmt":function(t){return ke(t,n,q)},"html-num":function(t){return ke(t,n,V)},"html-num-fmt":function(t){return ke(t,n,V,q)}},function(t,e){p.type.order[t+n+"-pre"]=e,t.match(/^html\-/)&&(p.type.search[t+n]=p.type.search.html)})}P.extend(p.type.order,{"date-pre":function(t){t=Date.parse(t);return isNaN(t)?-1/0:t},"html-pre":function(t){return h(t)?"":t.replace?t.replace(/<.*?>/g,"").toLowerCase():t+""},"string-pre":function(t){return h(t)?"":"string"==typeof t?t.toLowerCase():t.toString?t.toString():""},"string-asc":function(t,e){return t<e?-1:e<t?1:0},"string-desc":function(t,e){return t<e?1:e<t?-1:0}}),Me(""),P.extend(!0,w.ext.renderer,{header:{_:function(r,o,i,l){P(r.nTable).on("order.dt.DT",function(t,e,n,a){r===e&&(e=i.idx,o.removeClass(l.sSortAsc+" "+l.sSortDesc).addClass("asc"==a[e]?l.sSortAsc:"desc"==a[e]?l.sSortDesc:i.sSortingClass))})},jqueryui:function(r,o,i,l){P("<div/>").addClass(l.sSortJUIWrapper).append(o.contents()).append(P("<span/>").addClass(l.sSortIcon+" "+i.sSortingClassJUI)).appendTo(o),P(r.nTable).on("order.dt.DT",function(t,e,n,a){r===e&&(e=i.idx,o.removeClass(l.sSortAsc+" "+l.sSortDesc).addClass("asc"==a[e]?l.sSortAsc:"desc"==a[e]?l.sSortDesc:i.sSortingClass),o.find("span."+l.sSortIcon).removeClass(l.sSortJUIAsc+" "+l.sSortJUIDesc+" "+l.sSortJUI+" "+l.sSortJUIAscAllowed+" "+l.sSortJUIDescAllowed).addClass("asc"==a[e]?l.sSortJUIAsc:"desc"==a[e]?l.sSortJUIDesc:i.sSortingClassJUI))})}}});function We(t){return"string"==typeof(t=Array.isArray(t)?t.join(","):t)?t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):t}function Ee(t,e,n,a,r){return j.moment?t[e](r):j.luxon?t[n](r):a?t[a](r):t}var Be=!1;function Ue(t,e,n){var a;if(j.moment){if(!(a=j.moment.utc(t,e,n,!0)).isValid())return null}else if(j.luxon){if(!(a=e&&"string"==typeof t?j.luxon.DateTime.fromFormat(t,e):j.luxon.DateTime.fromISO(t)).isValid)return null;a.setLocale(n)}else e?(Be||alert("DataTables warning: Formatted date without Moment.js or Luxon - https://datatables.net/tn/17"),Be=!0):a=new Date(t);return a}function Ve(s){return function(a,r,o,i){0===arguments.length?(o="en",a=r=null):1===arguments.length?(o="en",r=a,a=null):2===arguments.length&&(o=r,r=a,a=null);var l="datetime-"+r;return w.ext.type.order[l]||(w.ext.type.detect.unshift(function(t){return t===l&&l}),w.ext.type.order[l+"-asc"]=function(t,e){t=t.valueOf(),e=e.valueOf();return t===e?0:t<e?-1:1},w.ext.type.order[l+"-desc"]=function(t,e){t=t.valueOf(),e=e.valueOf();return t===e?0:e<t?-1:1}),function(t,e){var n;return null!==t&&t!==N||(t="--now"===i?(n=new Date,new Date(Date.UTC(n.getFullYear(),n.getMonth(),n.getDate(),n.getHours(),n.getMinutes(),n.getSeconds()))):""),"type"===e?l:""===t?"sort"!==e?"":Ue("0000-01-01 00:00:00",null,o):!(null===r||a!==r||"sort"===e||"type"===e||t instanceof Date)||null===(n=Ue(t,a,o))?t:"sort"===e?n:(t=null===r?Ee(n,"toDate","toJSDate","")[s]():Ee(n,"format","toFormat","toISOString",r),"display"===e?We(t):t)}}}var Xe=",",Je=".";if(Intl)try{for(var qe=(new Intl.NumberFormat).formatToParts(100000.1),n=0;n<qe.length;n++)"group"===qe[n].type?Xe=qe[n].value:"decimal"===qe[n].type&&(Je=qe[n].value)}catch(t){}function Ge(e){return function(){var t=[ge(this[w.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return w.ext.internal[e].apply(this,t)}}return w.datetime=function(n,a){var r="datetime-detect-"+n;a=a||"en",w.ext.type.order[r]||(w.ext.type.detect.unshift(function(t){var e=Ue(t,n,a);return!(""!==t&&!e)&&r}),w.ext.type.order[r+"-pre"]=function(t){return Ue(t,n,a)||0})},w.render={date:Ve("toLocaleDateString"),datetime:Ve("toLocaleString"),time:Ve("toLocaleTimeString"),number:function(a,r,o,i,l){return null!==a&&a!==N||(a=Xe),null!==r&&r!==N||(r=Je),{display:function(t){if("number"!=typeof t&&"string"!=typeof t)return t;if(""===t||null===t)return t;var e=t<0?"-":"",n=parseFloat(t);if(isNaN(n))return We(t);n=n.toFixed(o),t=Math.abs(n);n=parseInt(t,10),t=o?r+(t-n).toFixed(o).substring(2):"";return(e=0===n&&0===parseFloat(t)?"":e)+(i||"")+n.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+t+(l||"")}}},text:function(){return{display:We,filter:We}}},P.extend(w.ext.internal,{_fnExternApiFunc:Ge,_fnBuildAjax:Tt,_fnAjaxUpdate:xt,_fnAjaxParameters:At,_fnAjaxUpdateDraw:It,_fnAjaxDataSrc:Ft,_fnAddColumn:nt,_fnColumnOptions:at,_fnAdjustColumnSizing:O,_fnVisibleToColumnIndex:rt,_fnColumnIndexToVisible:ot,_fnVisbleColumns:T,_fnGetColumns:it,_fnColumnTypes:lt,_fnApplyColumnDefs:st,_fnHungarianMap:i,_fnCamelToHungarian:C,_fnLanguageCompat:Z,_fnBrowserDetect:tt,_fnAddData:x,_fnAddTr:ut,_fnNodeToDataIndex:function(t,e){return e._DT_RowIndex!==N?e._DT_RowIndex:null},_fnNodeToColumnIndex:function(t,e,n){return P.inArray(n,t.aoData[e].anCells)},_fnGetCellData:S,_fnSetCellData:ct,_fnSplitObjNotation:dt,_fnGetObjectDataFn:A,_fnSetObjectDataFn:b,_fnGetDataMaster:ht,_fnClearTable:pt,_fnDeleteIndex:gt,_fnInvalidate:bt,_fnGetRowElements:mt,_fnCreateTr:St,_fnBuildHead:yt,_fnDrawHead:Dt,_fnDraw:v,_fnReDraw:u,_fnAddOptionsHtml:_t,_fnDetectHeader:wt,_fnGetUniqueThs:Ct,_fnFeatureHtmlFilter:Lt,_fnFilterComplete:Rt,_fnFilterCustom:Pt,_fnFilterColumn:jt,_fnFilter:Nt,_fnFilterCreateSearch:Ht,_fnEscapeRegex:Ot,_fnFilterData:Wt,_fnFeatureHtmlInfo:Ut,_fnUpdateInfo:Vt,_fnInfoMacros:Xt,_fnInitialise:Jt,_fnInitComplete:qt,_fnLengthChange:Gt,_fnFeatureHtmlLength:$t,_fnFeatureHtmlPaginate:zt,_fnPageChange:Yt,_fnFeatureHtmlProcessing:Zt,_fnProcessingDisplay:D,_fnFeatureHtmlTable:Kt,_fnScrollDraw:Qt,_fnApplyToChildren:k,_fnCalculateColumnWidths:ee,_fnThrottle:ne,_fnConvertToWidth:ae,_fnGetWidestNode:re,_fnGetMaxLenString:oe,_fnStringToCss:M,_fnSortFlatten:I,_fnSort:ie,_fnSortAria:le,_fnSortListener:se,_fnSortAttachListener:ue,_fnSortingClasses:ce,_fnSortData:fe,_fnSaveState:de,_fnLoadState:he,_fnImplementState:pe,_fnSettingsFromNode:ge,_fnLog:W,_fnMap:F,_fnBindAction:me,_fnCallbackReg:L,_fnCallbackFire:R,_fnLengthOverflow:Se,_fnRenderer:ve,_fnDataSource:E,_fnRowAttributes:vt,_fnExtend:be,_fnCalculateEnd:function(){}}),((P.fn.dataTable=w).$=P).fn.dataTableSettings=w.settings,P.fn.dataTableExt=w.ext,P.fn.DataTable=function(t){return P(this).dataTable(t).api()},P.each(w,function(t,e){P.fn.DataTable[t]=e}),w}); \ No newline at end of file diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 1d6f8559d..23963a6e1 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -21,7 +21,7 @@ <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script> {{-- <link rel="stylesheet" href="{{asset('css/adminlte.min.css')}}"> --}} - <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.10.24/datatables.min.css" /> + href="{{ asset('plugins/datatables/jquery.dataTables.min.css') }}"> {{-- summernote --}} <link rel="stylesheet" href="{{ asset('plugins/summernote/summernote-bs4.min.css') }}"> @@ -474,7 +474,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <!-- Scripts --> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@10.14.1/dist/sweetalert2.all.min.js"></script> - <script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.24/datatables.min.js"></script> + <script src="{{ asset('plugins/datatables/jquery.dataTables.min.js') }}"></script> <!-- Summernote --> <script src="{{ asset('plugins/summernote/summernote-bs4.min.js') }}"></script> <!-- select2 --> From c8ce65c8e39438b3a569d3659d2c4e102874b16b Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sat, 29 Apr 2023 23:35:31 +0200 Subject: [PATCH 097/514] Create v1.13.4 --- public/plugins/datatables/v1.13.4 | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/plugins/datatables/v1.13.4 diff --git a/public/plugins/datatables/v1.13.4 b/public/plugins/datatables/v1.13.4 new file mode 100644 index 000000000..e69de29bb From 4f42c23a4ef62eb90bc1388590c676154b99cfdd Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sat, 29 Apr 2023 23:38:20 +0200 Subject: [PATCH 098/514] update alpine to local --- public/plugins/alpinejs/3.12.0_cdn.min.js | 5 +++++ themes/default/views/layouts/main.blade.php | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 public/plugins/alpinejs/3.12.0_cdn.min.js diff --git a/public/plugins/alpinejs/3.12.0_cdn.min.js b/public/plugins/alpinejs/3.12.0_cdn.min.js new file mode 100644 index 000000000..d806d3234 --- /dev/null +++ b/public/plugins/alpinejs/3.12.0_cdn.min.js @@ -0,0 +1,5 @@ +(()=>{var Ye=!1,Ze=!1,V=[],Qe=-1;function Bt(e){hn(e)}function hn(e){V.includes(e)||V.push(e),_n()}function ye(e){let t=V.indexOf(e);t!==-1&&t>Qe&&V.splice(t,1)}function _n(){!Ze&&!Ye&&(Ye=!0,queueMicrotask(gn))}function gn(){Ye=!1,Ze=!0;for(let e=0;e<V.length;e++)V[e](),Qe=e;V.length=0,Qe=-1,Ze=!1}var C,P,L,et,Xe=!0;function Kt(e){Xe=!1,e(),Xe=!0}function zt(e){C=e.reactive,L=e.release,P=t=>e.effect(t,{scheduler:r=>{Xe?Bt(r):r()}}),et=e.raw}function tt(e){P=e}function Vt(e){let t=()=>{};return[n=>{let i=P(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),L(i))},i},()=>{t()}]}var Ht=[],qt=[],Ut=[];function Wt(e){Ut.push(e)}function we(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,qt.push(t))}function Gt(e){Ht.push(e)}function Jt(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function nt(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}var it=new MutationObserver(ct),ot=!1;function se(){it.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ot=!0}function st(){xn(),it.disconnect(),ot=!1}var oe=[],rt=!1;function xn(){oe=oe.concat(it.takeRecords()),oe.length&&!rt&&(rt=!0,queueMicrotask(()=>{yn(),rt=!1}))}function yn(){ct(oe),oe.length=0}function h(e){if(!ot)return e();st();let t=e();return se(),t}var at=!1,be=[];function Yt(){at=!0}function Zt(){at=!1,ct(be),be=[]}function ct(e){if(at){be=be.concat(e);return}let t=[],r=[],n=new Map,i=new Map;for(let o=0;o<e.length;o++)if(!e[o].target._x_ignoreMutationObserver&&(e[o].type==="childList"&&(e[o].addedNodes.forEach(s=>s.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{nt(s,o)}),n.forEach((o,s)=>{Ht.forEach(a=>a(s,o))});for(let o of r)if(!t.includes(o)&&(qt.forEach(s=>s(o)),o._x_cleanups))for(;o._x_cleanups.length;)o._x_cleanups.pop()();t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.includes(o)||o.isConnected&&(delete o._x_ignoreSelf,delete o._x_ignore,Ut.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function Ee(e){return j($(e))}function R(e,t,r){return e._x_dataStack=[t,...$(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function lt(e,t){let r=e._x_dataStack[0];Object.entries(t).forEach(([n,i])=>{r[n]=i})}function $(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?$(e.host):e.parentNode?$(e.parentNode):[]}function j(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(r=>Object.keys(r)))),has:(r,n)=>e.some(i=>i.hasOwnProperty(n)),get:(r,n)=>(e.find(i=>{if(i.hasOwnProperty(n)){let o=Object.getOwnPropertyDescriptor(i,n);if(o.get&&o.get._x_alreadyBound||o.set&&o.set._x_alreadyBound)return!0;if((o.get||o.set)&&o.enumerable){let s=o.get,a=o.set,c=o;s=s&&s.bind(t),a=a&&a.bind(t),s&&(s._x_alreadyBound=!0),a&&(a._x_alreadyBound=!0),Object.defineProperty(i,n,{...c,get:s,set:a})}return!0}return!1})||{})[n],set:(r,n,i)=>{let o=e.find(s=>s.hasOwnProperty(n));return o?o[n]=i:e[e.length-1][n]=i,!0}});return t}function ve(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Se(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>bn(n,i),s=>ut(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function bn(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function ut(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),ut(e[t[0]],t.slice(1),r)}}var Qt={};function y(e,t){Qt[e]=t}function ae(e,t){return Object.entries(Qt).forEach(([r,n])=>{Object.defineProperty(e,`$${r}`,{get(){let[i,o]=ft(t);return i={interceptor:Se,...i},we(t,o),n(t,i)},enumerable:!1})}),e}function Xt(e,t,r,...n){try{return r(...n)}catch(i){Z(i,e,t)}}function Z(e,t,r=void 0){Object.assign(e,{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message} + +${r?'Expression: "'+r+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var Ae=!0;function er(e){let t=Ae;Ae=!1,e(),Ae=t}function I(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return tr(...e)}var tr=pt;function rr(e){tr=e}function pt(e,t){let r={};ae(r,e);let n=[r,...$(e)],i=typeof t=="function"?wn(n,t):vn(n,t,e);return Xt.bind(null,e,t,i)}function wn(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(j([n,...e]),i);Oe(r,o)}}var dt={};function En(e,t){if(dt[e])return dt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(async()=>{ ${e} })()`:e,o=(()=>{try{return new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`)}catch(s){return Z(s,t,e),Promise.resolve()}})();return dt[e]=o,o}function vn(e,t,r){let n=En(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=j([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>Z(l,r,t));n.finished?(Oe(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Oe(i,l,a,s,r)}).catch(l=>Z(l,r,t)).finally(()=>n.result=void 0)}}}function Oe(e,t,r,n,i){if(Ae&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Oe(e,s,r,n)).catch(s=>Z(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var gt="x-";function S(e=""){return gt+e}function nr(e){gt=e}var mt={};function p(e,t){return mt[e]=t,{before(r){if(!mt[r]){console.warn("Cannot find directive `${directive}`. `${name}` will use the default order of execution");return}let n=H.indexOf(r);H.splice(n>=0?n:H.indexOf("DEFAULT"),0,e)}}}function le(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=xt(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(sr((o,s)=>n[o]=s)).filter(cr).map(An(n,r)).sort(On).map(o=>Sn(e,o))}function xt(e){return Array.from(e).map(sr()).filter(t=>!cr(t))}var ht=!1,ce=new Map,ir=Symbol();function or(e){ht=!0;let t=Symbol();ir=t,ce.set(t,[]);let r=()=>{for(;ce.get(t).length;)ce.get(t).shift()();ce.delete(t)},n=()=>{ht=!1,r()};e(r),n()}function ft(e){let t=[],r=a=>t.push(a),[n,i]=Vt(e);return t.push(i),[{Alpine:F,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:I.bind(I,e)},()=>t.forEach(a=>a())]}function Sn(e,t){let r=()=>{},n=mt[t.type]||r,[i,o]=ft(e);Jt(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),ht?ce.get(ir).push(n):n())};return s.runCleanups=o,s}var Ce=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Te=e=>e;function sr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=ar.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var ar=[];function Q(e){ar.push(e)}function cr({name:e}){return lr().test(e)}var lr=()=>new RegExp(`^${gt}([^:^.]+)\\b`);function An(e,t){return({name:r,value:n})=>{let i=r.match(lr()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var _t="DEFAULT",H=["ignore","ref","data","id","bind","init","for","model","modelable","transition","show","if",_t,"teleport"];function On(e,t){let r=H.indexOf(e.type)===-1?_t:e.type,n=H.indexOf(t.type)===-1?_t:t.type;return H.indexOf(r)-H.indexOf(n)}function q(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function A(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>A(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)A(n,t,!1),n=n.nextElementSibling}function T(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function ur(){document.body||T("Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine's `<script>` tag?"),q(document,"alpine:init"),q(document,"alpine:initializing"),se(),Wt(t=>v(t,A)),we(t=>bt(t)),Gt((t,r)=>{le(t,r).forEach(n=>n())});let e=t=>!U(t.parentElement,!0);Array.from(document.querySelectorAll(pr())).filter(e).forEach(t=>{v(t)}),q(document,"alpine:initialized")}var yt=[],fr=[];function dr(){return yt.map(e=>e())}function pr(){return yt.concat(fr).map(e=>e())}function Me(e){yt.push(e)}function Re(e){fr.push(e)}function U(e,t=!1){return X(e,r=>{if((t?pr():dr()).some(i=>r.matches(i)))return!0})}function X(e,t){if(e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),!!e.parentElement)return X(e.parentElement,t)}}function mr(e){return dr().some(t=>e.matches(t))}var hr=[];function _r(e){hr.push(e)}function v(e,t=A,r=()=>{}){or(()=>{t(e,(n,i)=>{r(n,i),hr.forEach(o=>o(n,i)),le(n,n.attributes).forEach(o=>o()),n._x_ignore&&i()})})}function bt(e){A(e,t=>nt(t))}var wt=[],Et=!1;function ee(e=()=>{}){return queueMicrotask(()=>{Et||setTimeout(()=>{Ne()})}),new Promise(t=>{wt.push(()=>{e(),t()})})}function Ne(){for(Et=!1;wt.length;)wt.shift()()}function gr(){Et=!0}function ue(e,t){return Array.isArray(t)?xr(e,t.join(" ")):typeof t=="object"&&t!==null?Cn(e,t):typeof t=="function"?ue(e,t()):xr(e,t)}function xr(e,t){let r=o=>o.split(" ").filter(Boolean),n=o=>o.split(" ").filter(s=>!e.classList.contains(s)).filter(Boolean),i=o=>(e.classList.add(...o),()=>{e.classList.remove(...o)});return t=t===!0?t="":t||"",i(n(t))}function Cn(e,t){let r=a=>a.split(" ").filter(Boolean),n=Object.entries(t).flatMap(([a,c])=>c?r(a):!1).filter(Boolean),i=Object.entries(t).flatMap(([a,c])=>c?!1:r(a)).filter(Boolean),o=[],s=[];return i.forEach(a=>{e.classList.contains(a)&&(e.classList.remove(a),s.push(a))}),n.forEach(a=>{e.classList.contains(a)||(e.classList.add(a),o.push(a))}),()=>{s.forEach(a=>e.classList.add(a)),o.forEach(a=>e.classList.remove(a))}}function W(e,t){return typeof t=="object"&&t!==null?Tn(e,t):Mn(e,t)}function Tn(e,t){let r={};return Object.entries(t).forEach(([n,i])=>{r[n]=e.style[n],n.startsWith("--")||(n=Rn(n)),e.style.setProperty(n,i)}),setTimeout(()=>{e.style.length===0&&e.removeAttribute("style")}),()=>{W(e,r)}}function Mn(e,t){let r=e.getAttribute("style",t);return e.setAttribute("style",t),()=>{e.setAttribute("style",r||"")}}function Rn(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function fe(e,t=()=>{}){let r=!1;return function(){r?t.apply(this,arguments):(r=!0,e.apply(this,arguments))}}p("transition",(e,{value:t,modifiers:r,expression:n},{evaluate:i})=>{typeof n=="function"&&(n=i(n)),n?Nn(e,n,t):Dn(e,r,t)});function Nn(e,t,r){yr(e,ue,""),{enter:i=>{e._x_transition.enter.during=i},"enter-start":i=>{e._x_transition.enter.start=i},"enter-end":i=>{e._x_transition.enter.end=i},leave:i=>{e._x_transition.leave.during=i},"leave-start":i=>{e._x_transition.leave.start=i},"leave-end":i=>{e._x_transition.leave.end=i}}[r](t)}function Dn(e,t,r){yr(e,W);let n=!t.includes("in")&&!t.includes("out")&&!r,i=n||t.includes("in")||["enter"].includes(r),o=n||t.includes("out")||["leave"].includes(r);t.includes("in")&&!n&&(t=t.filter((_,b)=>b<t.indexOf("out"))),t.includes("out")&&!n&&(t=t.filter((_,b)=>b>t.indexOf("out")));let s=!t.includes("opacity")&&!t.includes("scale"),a=s||t.includes("opacity"),c=s||t.includes("scale"),l=a?0:1,u=c?de(t,"scale",95)/100:1,d=de(t,"delay",0),m=de(t,"origin","center"),w="opacity, transform",k=de(t,"duration",150)/1e3,ge=de(t,"duration",75)/1e3,f="cubic-bezier(0.4, 0.0, 0.2, 1)";i&&(e._x_transition.enter.during={transformOrigin:m,transitionDelay:d,transitionProperty:w,transitionDuration:`${k}s`,transitionTimingFunction:f},e._x_transition.enter.start={opacity:l,transform:`scale(${u})`},e._x_transition.enter.end={opacity:1,transform:"scale(1)"}),o&&(e._x_transition.leave.during={transformOrigin:m,transitionDelay:d,transitionProperty:w,transitionDuration:`${ge}s`,transitionTimingFunction:f},e._x_transition.leave.start={opacity:1,transform:"scale(1)"},e._x_transition.leave.end={opacity:l,transform:`scale(${u})`})}function yr(e,t,r={}){e._x_transition||(e._x_transition={enter:{during:r,start:r,end:r},leave:{during:r,start:r,end:r},in(n=()=>{},i=()=>{}){De(e,t,{during:this.enter.during,start:this.enter.start,end:this.enter.end},n,i)},out(n=()=>{},i=()=>{}){De(e,t,{during:this.leave.during,start:this.leave.start,end:this.leave.end},n,i)}})}window.Element.prototype._x_toggleAndCascadeWithTransitions=function(e,t,r,n){let i=document.visibilityState==="visible"?requestAnimationFrame:setTimeout,o=()=>i(r);if(t){e._x_transition&&(e._x_transition.enter||e._x_transition.leave)?e._x_transition.enter&&(Object.entries(e._x_transition.enter.during).length||Object.entries(e._x_transition.enter.start).length||Object.entries(e._x_transition.enter.end).length)?e._x_transition.in(r):o():e._x_transition?e._x_transition.in(r):o();return}e._x_hidePromise=e._x_transition?new Promise((s,a)=>{e._x_transition.out(()=>{},()=>s(n)),e._x_transitioning.beforeCancel(()=>a({isFromCancelledTransition:!0}))}):Promise.resolve(n),queueMicrotask(()=>{let s=br(e);s?(s._x_hideChildren||(s._x_hideChildren=[]),s._x_hideChildren.push(e)):i(()=>{let a=c=>{let l=Promise.all([c._x_hidePromise,...(c._x_hideChildren||[]).map(a)]).then(([u])=>u());return delete c._x_hidePromise,delete c._x_hideChildren,l};a(e).catch(c=>{if(!c.isFromCancelledTransition)throw c})})})};function br(e){let t=e.parentNode;if(t)return t._x_hidePromise?t:br(t)}function De(e,t,{during:r,start:n,end:i}={},o=()=>{},s=()=>{}){if(e._x_transitioning&&e._x_transitioning.cancel(),Object.keys(r).length===0&&Object.keys(n).length===0&&Object.keys(i).length===0){o(),s();return}let a,c,l;Pn(e,{start(){a=t(e,n)},during(){c=t(e,r)},before:o,end(){a(),l=t(e,i)},after:s,cleanup(){c(),l()}})}function Pn(e,t){let r,n,i,o=fe(()=>{h(()=>{r=!0,n||t.before(),i||(t.end(),Ne()),t.after(),e.isConnected&&t.cleanup(),delete e._x_transitioning})});e._x_transitioning={beforeCancels:[],beforeCancel(s){this.beforeCancels.push(s)},cancel:fe(function(){for(;this.beforeCancels.length;)this.beforeCancels.shift()();o()}),finish:o},h(()=>{t.start(),t.during()}),gr(),requestAnimationFrame(()=>{if(r)return;let s=Number(getComputedStyle(e).transitionDuration.replace(/,.*/,"").replace("s",""))*1e3,a=Number(getComputedStyle(e).transitionDelay.replace(/,.*/,"").replace("s",""))*1e3;s===0&&(s=Number(getComputedStyle(e).animationDuration.replace("s",""))*1e3),h(()=>{t.before()}),n=!0,requestAnimationFrame(()=>{r||(h(()=>{t.end()}),Ne(),setTimeout(e._x_transitioning.finish,s+a),i=!0)})})}function de(e,t,r){if(e.indexOf(t)===-1)return r;let n=e[e.indexOf(t)+1];if(!n||t==="scale"&&isNaN(n))return r;if(t==="duration"){let i=n.match(/([0-9]+)ms/);if(i)return i[1]}return t==="origin"&&["top","right","left","center","bottom"].includes(e[e.indexOf(t)+2])?[n,e[e.indexOf(t)+2]].join(" "):n}var te=!1;function N(e,t=()=>{}){return(...r)=>te?t(...r):e(...r)}function wr(e){return(...t)=>te&&e(...t)}function Er(e,t){t._x_dataStack||(t._x_dataStack=e._x_dataStack),te=!0,kn(()=>{In(t)}),te=!1}function In(e){let t=!1;v(e,(n,i)=>{A(n,(o,s)=>{if(t&&mr(o))return s();t=!0,i(o,s)})})}function kn(e){let t=P;tt((r,n)=>{let i=t(r);return L(i),()=>{}}),e(),tt(t)}function pe(e,t,r,n=[]){switch(e._x_bindings||(e._x_bindings=C({})),e._x_bindings[t]=r,t=n.includes("camel")?zn(t):t,t){case"value":Ln(e,r);break;case"style":jn(e,r);break;case"class":$n(e,r);break;default:Fn(e,t,r);break}}function Ln(e,t){if(e.type==="radio")e.attributes.value===void 0&&(e.value=t),window.fromModel&&(e.checked=vr(e.value,t));else if(e.type==="checkbox")Number.isInteger(t)?e.value=t:!Number.isInteger(t)&&!Array.isArray(t)&&typeof t!="boolean"&&![null,void 0].includes(t)?e.value=String(t):Array.isArray(t)?e.checked=t.some(r=>vr(r,e.value)):e.checked=!!t;else if(e.tagName==="SELECT")Kn(e,t);else{if(e.value===t)return;e.value=t}}function $n(e,t){e._x_undoAddedClasses&&e._x_undoAddedClasses(),e._x_undoAddedClasses=ue(e,t)}function jn(e,t){e._x_undoAddedStyles&&e._x_undoAddedStyles(),e._x_undoAddedStyles=W(e,t)}function Fn(e,t,r){[null,void 0,!1].includes(r)&&Vn(t)?e.removeAttribute(t):(Sr(t)&&(r=t),Bn(e,t,r))}function Bn(e,t,r){e.getAttribute(t)!=r&&e.setAttribute(t,r)}function Kn(e,t){let r=[].concat(t).map(n=>n+"");Array.from(e.options).forEach(n=>{n.selected=r.includes(n.value)})}function zn(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function vr(e,t){return e==t}function Sr(e){return["disabled","checked","required","readonly","hidden","open","selected","autofocus","itemscope","multiple","novalidate","allowfullscreen","allowpaymentrequest","formnovalidate","autoplay","controls","loop","muted","playsinline","default","ismap","reversed","async","defer","nomodule"].includes(e)}function Vn(e){return!["aria-pressed","aria-checked","aria-expanded","aria-selected"].includes(e)}function Ar(e,t,r){if(e._x_bindings&&e._x_bindings[t]!==void 0)return e._x_bindings[t];let n=e.getAttribute(t);return n===null?typeof r=="function"?r():r:n===""?!0:Sr(t)?!![t,"true"].includes(n):n}function Pe(e,t){var r;return function(){var n=this,i=arguments,o=function(){r=null,e.apply(n,i)};clearTimeout(r),r=setTimeout(o,t)}}function Ie(e,t){let r;return function(){let n=this,i=arguments;r||(e.apply(n,i),r=!0,setTimeout(()=>r=!1,t))}}function Or(e){e(F)}var G={},Cr=!1;function Tr(e,t){if(Cr||(G=C(G),Cr=!0),t===void 0)return G[e];G[e]=t,typeof t=="object"&&t!==null&&t.hasOwnProperty("init")&&typeof t.init=="function"&&G[e].init(),ve(G[e])}function Mr(){return G}var Rr={};function Nr(e,t){let r=typeof t!="function"?()=>t:t;e instanceof Element?vt(e,r()):Rr[e]=r}function Dr(e){return Object.entries(Rr).forEach(([t,r])=>{Object.defineProperty(e,t,{get(){return(...n)=>r(...n)}})}),e}function vt(e,t,r){let n=[];for(;n.length;)n.pop()();let i=Object.entries(t).map(([s,a])=>({name:s,value:a})),o=xt(i);i=i.map(s=>o.find(a=>a.name===s.name)?{name:`x-bind:${s.name}`,value:`"${s.value}"`}:s),le(e,i,r).map(s=>{n.push(s.runCleanups),s()})}var Pr={};function Ir(e,t){Pr[e]=t}function kr(e,t){return Object.entries(Pr).forEach(([r,n])=>{Object.defineProperty(e,r,{get(){return(...i)=>n.bind(t)(...i)},enumerable:!1})}),e}var Hn={get reactive(){return C},get release(){return L},get effect(){return P},get raw(){return et},version:"3.12.0",flushAndStopDeferringMutations:Zt,dontAutoEvaluateFunctions:er,disableEffectScheduling:Kt,startObservingMutations:se,stopObservingMutations:st,setReactivityEngine:zt,closestDataStack:$,skipDuringClone:N,onlyDuringClone:wr,addRootSelector:Me,addInitSelector:Re,addScopeToNode:R,deferMutations:Yt,mapAttributes:Q,evaluateLater:x,interceptInit:_r,setEvaluator:rr,mergeProxies:j,findClosest:X,closestRoot:U,destroyTree:bt,interceptor:Se,transition:De,setStyles:W,mutateDom:h,directive:p,throttle:Ie,debounce:Pe,evaluate:I,initTree:v,nextTick:ee,prefixed:S,prefix:nr,plugin:Or,magic:y,store:Tr,start:ur,clone:Er,bound:Ar,$data:Ee,walk:A,data:Ir,bind:Nr},F=Hn;function St(e,t){let r=Object.create(null),n=e.split(",");for(let i=0;i<n.length;i++)r[n[i]]=!0;return t?i=>!!r[i.toLowerCase()]:i=>!!r[i]}var qn="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly";var cs=St(qn+",async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");var Lr=Object.freeze({}),ls=Object.freeze([]);var At=Object.assign;var Un=Object.prototype.hasOwnProperty,me=(e,t)=>Un.call(e,t),B=Array.isArray,re=e=>$r(e)==="[object Map]";var Wn=e=>typeof e=="string",ke=e=>typeof e=="symbol",he=e=>e!==null&&typeof e=="object";var Gn=Object.prototype.toString,$r=e=>Gn.call(e),Ot=e=>$r(e).slice(8,-1);var Le=e=>Wn(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e;var $e=e=>{let t=Object.create(null);return r=>t[r]||(t[r]=e(r))},Jn=/-(\w)/g,us=$e(e=>e.replace(Jn,(t,r)=>r?r.toUpperCase():"")),Yn=/\B([A-Z])/g,fs=$e(e=>e.replace(Yn,"-$1").toLowerCase()),Ct=$e(e=>e.charAt(0).toUpperCase()+e.slice(1)),ds=$e(e=>e?`on${Ct(e)}`:""),Tt=(e,t)=>e!==t&&(e===e||t===t);var Mt=new WeakMap,_e=[],D,J=Symbol("iterate"),Rt=Symbol("Map key iterate");function Zn(e){return e&&e._isEffect===!0}function jr(e,t=Lr){Zn(e)&&(e=e.raw);let r=Xn(e,t);return t.lazy||r(),r}function Fr(e){e.active&&(Br(e),e.options.onStop&&e.options.onStop(),e.active=!1)}var Qn=0;function Xn(e,t){let r=function(){if(!r.active)return e();if(!_e.includes(r)){Br(r);try{return ti(),_e.push(r),D=r,e()}finally{_e.pop(),Kr(),D=_e[_e.length-1]}}};return r.id=Qn++,r.allowRecurse=!!t.allowRecurse,r._isEffect=!0,r.active=!0,r.raw=e,r.deps=[],r.options=t,r}function Br(e){let{deps:t}=e;if(t.length){for(let r=0;r<t.length;r++)t[r].delete(e);t.length=0}}var ne=!0,Dt=[];function ei(){Dt.push(ne),ne=!1}function ti(){Dt.push(ne),ne=!0}function Kr(){let e=Dt.pop();ne=e===void 0?!0:e}function M(e,t,r){if(!ne||D===void 0)return;let n=Mt.get(e);n||Mt.set(e,n=new Map);let i=n.get(r);i||n.set(r,i=new Set),i.has(D)||(i.add(D),D.deps.push(i),D.options.onTrack&&D.options.onTrack({effect:D,target:e,type:t,key:r}))}function z(e,t,r,n,i,o){let s=Mt.get(e);if(!s)return;let a=new Set,c=u=>{u&&u.forEach(d=>{(d!==D||d.allowRecurse)&&a.add(d)})};if(t==="clear")s.forEach(c);else if(r==="length"&&B(e))s.forEach((u,d)=>{(d==="length"||d>=n)&&c(u)});else switch(r!==void 0&&c(s.get(r)),t){case"add":B(e)?Le(r)&&c(s.get("length")):(c(s.get(J)),re(e)&&c(s.get(Rt)));break;case"delete":B(e)||(c(s.get(J)),re(e)&&c(s.get(Rt)));break;case"set":re(e)&&c(s.get(J));break}let l=u=>{u.options.onTrigger&&u.options.onTrigger({effect:u,target:e,key:r,type:t,newValue:n,oldValue:i,oldTarget:o}),u.options.scheduler?u.options.scheduler(u):u()};a.forEach(l)}var ri=St("__proto__,__v_isRef,__isVue"),zr=new Set(Object.getOwnPropertyNames(Symbol).map(e=>Symbol[e]).filter(ke)),ni=Be(),ii=Be(!1,!0),oi=Be(!0),si=Be(!0,!0),Fe={};["includes","indexOf","lastIndexOf"].forEach(e=>{let t=Array.prototype[e];Fe[e]=function(...r){let n=g(this);for(let o=0,s=this.length;o<s;o++)M(n,"get",o+"");let i=t.apply(n,r);return i===-1||i===!1?t.apply(n,r.map(g)):i}});["push","pop","shift","unshift","splice"].forEach(e=>{let t=Array.prototype[e];Fe[e]=function(...r){ei();let n=t.apply(this,r);return Kr(),n}});function Be(e=!1,t=!1){return function(n,i,o){if(i==="__v_isReactive")return!e;if(i==="__v_isReadonly")return e;if(i==="__v_raw"&&o===(e?t?_i:rn:t?hi:tn).get(n))return n;let s=B(n);if(!e&&s&&me(Fe,i))return Reflect.get(Fe,i,o);let a=Reflect.get(n,i,o);return(ke(i)?zr.has(i):ri(i))||(e||M(n,"get",i),t)?a:Nt(a)?!s||!Le(i)?a.value:a:he(a)?e?nn(a):We(a):a}}var ai=Vr(),ci=Vr(!0);function Vr(e=!1){return function(r,n,i,o){let s=r[n];if(!e&&(i=g(i),s=g(s),!B(r)&&Nt(s)&&!Nt(i)))return s.value=i,!0;let a=B(r)&&Le(n)?Number(n)<r.length:me(r,n),c=Reflect.set(r,n,i,o);return r===g(o)&&(a?Tt(i,s)&&z(r,"set",n,i,s):z(r,"add",n,i)),c}}function li(e,t){let r=me(e,t),n=e[t],i=Reflect.deleteProperty(e,t);return i&&r&&z(e,"delete",t,void 0,n),i}function ui(e,t){let r=Reflect.has(e,t);return(!ke(t)||!zr.has(t))&&M(e,"has",t),r}function fi(e){return M(e,"iterate",B(e)?"length":J),Reflect.ownKeys(e)}var Hr={get:ni,set:ai,deleteProperty:li,has:ui,ownKeys:fi},qr={get:oi,set(e,t){return console.warn(`Set operation on key "${String(t)}" failed: target is readonly.`,e),!0},deleteProperty(e,t){return console.warn(`Delete operation on key "${String(t)}" failed: target is readonly.`,e),!0}},xs=At({},Hr,{get:ii,set:ci}),ys=At({},qr,{get:si}),Pt=e=>he(e)?We(e):e,It=e=>he(e)?nn(e):e,kt=e=>e,Ke=e=>Reflect.getPrototypeOf(e);function ze(e,t,r=!1,n=!1){e=e.__v_raw;let i=g(e),o=g(t);t!==o&&!r&&M(i,"get",t),!r&&M(i,"get",o);let{has:s}=Ke(i),a=n?kt:r?It:Pt;if(s.call(i,t))return a(e.get(t));if(s.call(i,o))return a(e.get(o));e!==i&&e.get(t)}function Ve(e,t=!1){let r=this.__v_raw,n=g(r),i=g(e);return e!==i&&!t&&M(n,"has",e),!t&&M(n,"has",i),e===i?r.has(e):r.has(e)||r.has(i)}function He(e,t=!1){return e=e.__v_raw,!t&&M(g(e),"iterate",J),Reflect.get(e,"size",e)}function Ur(e){e=g(e);let t=g(this);return Ke(t).has.call(t,e)||(t.add(e),z(t,"add",e,e)),this}function Wr(e,t){t=g(t);let r=g(this),{has:n,get:i}=Ke(r),o=n.call(r,e);o?en(r,n,e):(e=g(e),o=n.call(r,e));let s=i.call(r,e);return r.set(e,t),o?Tt(t,s)&&z(r,"set",e,t,s):z(r,"add",e,t),this}function Gr(e){let t=g(this),{has:r,get:n}=Ke(t),i=r.call(t,e);i?en(t,r,e):(e=g(e),i=r.call(t,e));let o=n?n.call(t,e):void 0,s=t.delete(e);return i&&z(t,"delete",e,void 0,o),s}function Jr(){let e=g(this),t=e.size!==0,r=re(e)?new Map(e):new Set(e),n=e.clear();return t&&z(e,"clear",void 0,void 0,r),n}function qe(e,t){return function(n,i){let o=this,s=o.__v_raw,a=g(s),c=t?kt:e?It:Pt;return!e&&M(a,"iterate",J),s.forEach((l,u)=>n.call(i,c(l),c(u),o))}}function je(e,t,r){return function(...n){let i=this.__v_raw,o=g(i),s=re(o),a=e==="entries"||e===Symbol.iterator&&s,c=e==="keys"&&s,l=i[e](...n),u=r?kt:t?It:Pt;return!t&&M(o,"iterate",c?Rt:J),{next(){let{value:d,done:m}=l.next();return m?{value:d,done:m}:{value:a?[u(d[0]),u(d[1])]:u(d),done:m}},[Symbol.iterator](){return this}}}}function K(e){return function(...t){{let r=t[0]?`on key "${t[0]}" `:"";console.warn(`${Ct(e)} operation ${r}failed: target is readonly.`,g(this))}return e==="delete"?!1:this}}var Yr={get(e){return ze(this,e)},get size(){return He(this)},has:Ve,add:Ur,set:Wr,delete:Gr,clear:Jr,forEach:qe(!1,!1)},Zr={get(e){return ze(this,e,!1,!0)},get size(){return He(this)},has:Ve,add:Ur,set:Wr,delete:Gr,clear:Jr,forEach:qe(!1,!0)},Qr={get(e){return ze(this,e,!0)},get size(){return He(this,!0)},has(e){return Ve.call(this,e,!0)},add:K("add"),set:K("set"),delete:K("delete"),clear:K("clear"),forEach:qe(!0,!1)},Xr={get(e){return ze(this,e,!0,!0)},get size(){return He(this,!0)},has(e){return Ve.call(this,e,!0)},add:K("add"),set:K("set"),delete:K("delete"),clear:K("clear"),forEach:qe(!0,!0)},di=["keys","values","entries",Symbol.iterator];di.forEach(e=>{Yr[e]=je(e,!1,!1),Qr[e]=je(e,!0,!1),Zr[e]=je(e,!1,!0),Xr[e]=je(e,!0,!0)});function Ue(e,t){let r=t?e?Xr:Zr:e?Qr:Yr;return(n,i,o)=>i==="__v_isReactive"?!e:i==="__v_isReadonly"?e:i==="__v_raw"?n:Reflect.get(me(r,i)&&i in n?r:n,i,o)}var pi={get:Ue(!1,!1)},bs={get:Ue(!1,!0)},mi={get:Ue(!0,!1)},ws={get:Ue(!0,!0)};function en(e,t,r){let n=g(r);if(n!==r&&t.call(e,n)){let i=Ot(e);console.warn(`Reactive ${i} contains both the raw and reactive versions of the same object${i==="Map"?" as keys":""}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`)}}var tn=new WeakMap,hi=new WeakMap,rn=new WeakMap,_i=new WeakMap;function gi(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function xi(e){return e.__v_skip||!Object.isExtensible(e)?0:gi(Ot(e))}function We(e){return e&&e.__v_isReadonly?e:on(e,!1,Hr,pi,tn)}function nn(e){return on(e,!0,qr,mi,rn)}function on(e,t,r,n,i){if(!he(e))return console.warn(`value cannot be made reactive: ${String(e)}`),e;if(e.__v_raw&&!(t&&e.__v_isReactive))return e;let o=i.get(e);if(o)return o;let s=xi(e);if(s===0)return e;let a=new Proxy(e,s===2?n:r);return i.set(e,a),a}function g(e){return e&&g(e.__v_raw)||e}function Nt(e){return Boolean(e&&e.__v_isRef===!0)}y("nextTick",()=>ee);y("dispatch",e=>q.bind(q,e));y("watch",(e,{evaluateLater:t,effect:r})=>(n,i)=>{let o=t(n),s=!0,a,c=r(()=>o(l=>{JSON.stringify(l),s?a=l:queueMicrotask(()=>{i(l,a),a=l}),s=!1}));e._x_effects.delete(c)});y("store",Mr);y("data",e=>Ee(e));y("root",e=>U(e));y("refs",e=>(e._x_refs_proxy||(e._x_refs_proxy=j(yi(e))),e._x_refs_proxy));function yi(e){let t=[],r=e;for(;r;)r._x_refs&&t.push(r._x_refs),r=r.parentNode;return t}var Lt={};function $t(e){return Lt[e]||(Lt[e]=0),++Lt[e]}function sn(e,t){return X(e,r=>{if(r._x_ids&&r._x_ids[t])return!0})}function an(e,t){e._x_ids||(e._x_ids={}),e._x_ids[t]||(e._x_ids[t]=$t(t))}y("id",e=>(t,r=null)=>{let n=sn(e,t),i=n?n._x_ids[t]:$t(t);return r?`${t}-${i}-${r}`:`${t}-${i}`});y("el",e=>e);cn("Focus","focus","focus");cn("Persist","persist","persist");function cn(e,t,r){y(t,n=>T(`You can't use [$${directiveName}] without first installing the "${e}" plugin here: https://alpinejs.dev/plugins/${r}`,n))}function ln({get:e,set:t},{get:r,set:n}){let i=!0,o,s,a,c,l=P(()=>{let u,d;i?(u=e(),n(u),d=r(),i=!1):(u=e(),d=r(),a=JSON.stringify(u),c=JSON.stringify(d),a!==o?(d=r(),n(u),d=u):(t(d),u=d)),o=JSON.stringify(u),s=JSON.stringify(d)});return()=>{L(l)}}p("modelable",(e,{expression:t},{effect:r,evaluateLater:n,cleanup:i})=>{let o=n(t),s=()=>{let u;return o(d=>u=d),u},a=n(`${t} = __placeholder`),c=u=>a(()=>{},{scope:{__placeholder:u}}),l=s();c(l),queueMicrotask(()=>{if(!e._x_model)return;e._x_removeModelListeners.default();let u=e._x_model.get,d=e._x_model.set,m=ln({get(){return u()},set(w){d(w)}},{get(){return s()},set(w){c(w)}});i(m)})});var bi=document.createElement("div");p("teleport",(e,{modifiers:t,expression:r},{cleanup:n})=>{e.tagName.toLowerCase()!=="template"&&T("x-teleport can only be used on a <template> tag",e);let i=N(()=>document.querySelector(r),()=>bi)();i||T(`Cannot find x-teleport element for selector: "${r}"`);let o=e.content.cloneNode(!0).firstElementChild;e._x_teleport=o,o._x_teleportBack=e,e._x_forwardEvents&&e._x_forwardEvents.forEach(s=>{o.addEventListener(s,a=>{a.stopPropagation(),e.dispatchEvent(new a.constructor(a.type,a))})}),R(o,{},e),h(()=>{t.includes("prepend")?i.parentNode.insertBefore(o,i):t.includes("append")?i.parentNode.insertBefore(o,i.nextSibling):i.appendChild(o),v(o),o._x_ignore=!0}),n(()=>o.remove())});var un=()=>{};un.inline=(e,{modifiers:t},{cleanup:r})=>{t.includes("self")?e._x_ignoreSelf=!0:e._x_ignore=!0,r(()=>{t.includes("self")?delete e._x_ignoreSelf:delete e._x_ignore})};p("ignore",un);p("effect",(e,{expression:t},{effect:r})=>r(x(e,t)));function ie(e,t,r,n){let i=e,o=c=>n(c),s={},a=(c,l)=>u=>l(c,u);if(r.includes("dot")&&(t=wi(t)),r.includes("camel")&&(t=Ei(t)),r.includes("passive")&&(s.passive=!0),r.includes("capture")&&(s.capture=!0),r.includes("window")&&(i=window),r.includes("document")&&(i=document),r.includes("prevent")&&(o=a(o,(c,l)=>{l.preventDefault(),c(l)})),r.includes("stop")&&(o=a(o,(c,l)=>{l.stopPropagation(),c(l)})),r.includes("self")&&(o=a(o,(c,l)=>{l.target===e&&c(l)})),(r.includes("away")||r.includes("outside"))&&(i=document,o=a(o,(c,l)=>{e.contains(l.target)||l.target.isConnected!==!1&&(e.offsetWidth<1&&e.offsetHeight<1||e._x_isShown!==!1&&c(l))})),r.includes("once")&&(o=a(o,(c,l)=>{c(l),i.removeEventListener(t,o,s)})),o=a(o,(c,l)=>{Si(t)&&Ai(l,r)||c(l)}),r.includes("debounce")){let c=r[r.indexOf("debounce")+1]||"invalid-wait",l=Ge(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=Pe(o,l)}if(r.includes("throttle")){let c=r[r.indexOf("throttle")+1]||"invalid-wait",l=Ge(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=Ie(o,l)}return i.addEventListener(t,o,s),()=>{i.removeEventListener(t,o,s)}}function wi(e){return e.replace(/-/g,".")}function Ei(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function Ge(e){return!Array.isArray(e)&&!isNaN(e)}function vi(e){return[" ","_"].includes(e)?e:e.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[_\s]/,"-").toLowerCase()}function Si(e){return["keydown","keyup"].includes(e)}function Ai(e,t){let r=t.filter(o=>!["window","document","prevent","stop","once","capture"].includes(o));if(r.includes("debounce")){let o=r.indexOf("debounce");r.splice(o,Ge((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.includes("throttle")){let o=r.indexOf("throttle");r.splice(o,Ge((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.length===0||r.length===1&&fn(e.key).includes(r[0]))return!1;let i=["ctrl","shift","alt","meta","cmd","super"].filter(o=>r.includes(o));return r=r.filter(o=>!i.includes(o)),!(i.length>0&&i.filter(s=>((s==="cmd"||s==="super")&&(s="meta"),e[`${s}Key`])).length===i.length&&fn(e.key).includes(r[0]))}function fn(e){if(!e)return[];e=vi(e);let t={ctrl:"control",slash:"/",space:" ",spacebar:" ",cmd:"meta",esc:"escape",up:"arrow-up",down:"arrow-down",left:"arrow-left",right:"arrow-right",period:".",equal:"=",minus:"-",underscore:"_"};return t[e]=e,Object.keys(t).map(r=>{if(t[r]===e)return r}).filter(r=>r)}p("model",(e,{modifiers:t,expression:r},{effect:n,cleanup:i})=>{let o=e;t.includes("parent")&&(o=e.parentNode);let s=x(o,r),a;typeof r=="string"?a=x(o,`${r} = __placeholder`):typeof r=="function"&&typeof r()=="string"?a=x(o,`${r()} = __placeholder`):a=()=>{};let c=()=>{let m;return s(w=>m=w),dn(m)?m.get():m},l=m=>{let w;s(k=>w=k),dn(w)?w.set(m):a(()=>{},{scope:{__placeholder:m}})};t.includes("fill")&&e.hasAttribute("value")&&(c()===null||c()==="")&&l(e.value),typeof r=="string"&&e.type==="radio"&&h(()=>{e.hasAttribute("name")||e.setAttribute("name",r)});var u=e.tagName.toLowerCase()==="select"||["checkbox","radio"].includes(e.type)||t.includes("lazy")?"change":"input";let d=te?()=>{}:ie(e,u,t,m=>{l(Oi(e,t,m,c()))});if(e._x_removeModelListeners||(e._x_removeModelListeners={}),e._x_removeModelListeners.default=d,i(()=>e._x_removeModelListeners.default()),e.form){let m=ie(e.form,"reset",[],w=>{ee(()=>e._x_model&&e._x_model.set(e.value))});i(()=>m())}e._x_model={get(){return c()},set(m){l(m)}},e._x_forceModelUpdate=m=>{m=m===void 0?c():m,m===void 0&&typeof r=="string"&&r.match(/\./)&&(m=""),window.fromModel=!0,h(()=>pe(e,"value",m)),delete window.fromModel},n(()=>{let m=c();t.includes("unintrusive")&&document.activeElement.isSameNode(e)||e._x_forceModelUpdate(m)})});function Oi(e,t,r,n){return h(()=>{if(r instanceof CustomEvent&&r.detail!==void 0)return typeof r.detail<"u"?r.detail:r.target.value;if(e.type==="checkbox")if(Array.isArray(n)){let i=t.includes("number")?jt(r.target.value):r.target.value;return r.target.checked?n.concat([i]):n.filter(o=>!Ci(o,i))}else return r.target.checked;else{if(e.tagName.toLowerCase()==="select"&&e.multiple)return t.includes("number")?Array.from(r.target.selectedOptions).map(i=>{let o=i.value||i.text;return jt(o)}):Array.from(r.target.selectedOptions).map(i=>i.value||i.text);{let i=r.target.value;return t.includes("number")?jt(i):t.includes("trim")?i.trim():i}}})}function jt(e){let t=e?parseFloat(e):null;return Ti(t)?t:e}function Ci(e,t){return e==t}function Ti(e){return!Array.isArray(e)&&!isNaN(e)}function dn(e){return e!==null&&typeof e=="object"&&typeof e.get=="function"&&typeof e.set=="function"}p("cloak",e=>queueMicrotask(()=>h(()=>e.removeAttribute(S("cloak")))));Re(()=>`[${S("init")}]`);p("init",N((e,{expression:t},{evaluate:r})=>typeof t=="string"?!!t.trim()&&r(t,{},!1):r(t,{},!1)));p("text",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{h(()=>{e.textContent=o})})})});p("html",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{h(()=>{e.innerHTML=o,e._x_ignoreSelf=!0,v(e),delete e._x_ignoreSelf})})})});Q(Ce(":",Te(S("bind:"))));p("bind",(e,{value:t,modifiers:r,expression:n,original:i},{effect:o})=>{if(!t){let a={};Dr(a),x(e,n)(l=>{vt(e,l,i)},{scope:a});return}if(t==="key")return Mi(e,n);let s=x(e,n);o(()=>s(a=>{a===void 0&&typeof n=="string"&&n.match(/\./)&&(a=""),h(()=>pe(e,t,a,r))}))});function Mi(e,t){e._x_keyExpression=t}Me(()=>`[${S("data")}]`);p("data",N((e,{expression:t},{cleanup:r})=>{t=t===""?"{}":t;let n={};ae(n,e);let i={};kr(i,n);let o=I(e,t,{scope:i});(o===void 0||o===!0)&&(o={}),ae(o,e);let s=C(o);ve(s);let a=R(e,s);s.init&&I(e,s.init),r(()=>{s.destroy&&I(e,s.destroy),a()})}));p("show",(e,{modifiers:t,expression:r},{effect:n})=>{let i=x(e,r);e._x_doHide||(e._x_doHide=()=>{h(()=>{e.style.setProperty("display","none",t.includes("important")?"important":void 0)})}),e._x_doShow||(e._x_doShow=()=>{h(()=>{e.style.length===1&&e.style.display==="none"?e.removeAttribute("style"):e.style.removeProperty("display")})});let o=()=>{e._x_doHide(),e._x_isShown=!1},s=()=>{e._x_doShow(),e._x_isShown=!0},a=()=>setTimeout(s),c=fe(d=>d?s():o(),d=>{typeof e._x_toggleAndCascadeWithTransitions=="function"?e._x_toggleAndCascadeWithTransitions(e,d,s,o):d?a():o()}),l,u=!0;n(()=>i(d=>{!u&&d===l||(t.includes("immediate")&&(d?a():o()),c(d),l=d,u=!1)}))});p("for",(e,{expression:t},{effect:r,cleanup:n})=>{let i=Ni(t),o=x(e,i.items),s=x(e,e._x_keyExpression||"index");e._x_prevKeys=[],e._x_lookup={},r(()=>Ri(e,i,o,s)),n(()=>{Object.values(e._x_lookup).forEach(a=>a.remove()),delete e._x_prevKeys,delete e._x_lookup})});function Ri(e,t,r,n){let i=s=>typeof s=="object"&&!Array.isArray(s),o=e;r(s=>{Di(s)&&s>=0&&(s=Array.from(Array(s).keys(),f=>f+1)),s===void 0&&(s=[]);let a=e._x_lookup,c=e._x_prevKeys,l=[],u=[];if(i(s))s=Object.entries(s).map(([f,_])=>{let b=pn(t,_,f,s);n(E=>u.push(E),{scope:{index:f,...b}}),l.push(b)});else for(let f=0;f<s.length;f++){let _=pn(t,s[f],f,s);n(b=>u.push(b),{scope:{index:f,..._}}),l.push(_)}let d=[],m=[],w=[],k=[];for(let f=0;f<c.length;f++){let _=c[f];u.indexOf(_)===-1&&w.push(_)}c=c.filter(f=>!w.includes(f));let ge="template";for(let f=0;f<u.length;f++){let _=u[f],b=c.indexOf(_);if(b===-1)c.splice(f,0,_),d.push([ge,f]);else if(b!==f){let E=c.splice(f,1)[0],O=c.splice(b-1,1)[0];c.splice(f,0,O),c.splice(b,0,E),m.push([E,O])}else k.push(_);ge=_}for(let f=0;f<w.length;f++){let _=w[f];a[_]._x_effects&&a[_]._x_effects.forEach(ye),a[_].remove(),a[_]=null,delete a[_]}for(let f=0;f<m.length;f++){let[_,b]=m[f],E=a[_],O=a[b],Y=document.createElement("div");h(()=>{O.after(Y),E.after(O),O._x_currentIfEl&&O.after(O._x_currentIfEl),Y.before(E),E._x_currentIfEl&&E.after(E._x_currentIfEl),Y.remove()}),lt(O,l[u.indexOf(b)])}for(let f=0;f<d.length;f++){let[_,b]=d[f],E=_==="template"?o:a[_];E._x_currentIfEl&&(E=E._x_currentIfEl);let O=l[b],Y=u[b],xe=document.importNode(o.content,!0).firstElementChild;R(xe,C(O),o),h(()=>{E.after(xe),v(xe)}),typeof Y=="object"&&T("x-for key cannot be an object, it must be a string or an integer",o),a[Y]=xe}for(let f=0;f<k.length;f++)lt(a[k[f]],l[u.indexOf(k[f])]);o._x_prevKeys=u})}function Ni(e){let t=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,r=/^\s*\(|\)\s*$/g,n=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,i=e.match(n);if(!i)return;let o={};o.items=i[2].trim();let s=i[1].replace(r,"").trim(),a=s.match(t);return a?(o.item=s.replace(t,"").trim(),o.index=a[1].trim(),a[2]&&(o.collection=a[2].trim())):o.item=s,o}function pn(e,t,r,n){let i={};return/^\[.*\]$/.test(e.item)&&Array.isArray(t)?e.item.replace("[","").replace("]","").split(",").map(s=>s.trim()).forEach((s,a)=>{i[s]=t[a]}):/^\{.*\}$/.test(e.item)&&!Array.isArray(t)&&typeof t=="object"?e.item.replace("{","").replace("}","").split(",").map(s=>s.trim()).forEach(s=>{i[s]=t[s]}):i[e.item]=t,e.index&&(i[e.index]=r),e.collection&&(i[e.collection]=n),i}function Di(e){return!Array.isArray(e)&&!isNaN(e)}function mn(){}mn.inline=(e,{expression:t},{cleanup:r})=>{let n=U(e);n._x_refs||(n._x_refs={}),n._x_refs[t]=e,r(()=>delete n._x_refs[t])};p("ref",mn);p("if",(e,{expression:t},{effect:r,cleanup:n})=>{let i=x(e,t),o=()=>{if(e._x_currentIfEl)return e._x_currentIfEl;let a=e.content.cloneNode(!0).firstElementChild;return R(a,{},e),h(()=>{e.after(a),v(a)}),e._x_currentIfEl=a,e._x_undoIf=()=>{A(a,c=>{c._x_effects&&c._x_effects.forEach(ye)}),a.remove(),delete e._x_currentIfEl},a},s=()=>{e._x_undoIf&&(e._x_undoIf(),delete e._x_undoIf)};r(()=>i(a=>{a?o():s()})),n(()=>e._x_undoIf&&e._x_undoIf())});p("id",(e,{expression:t},{evaluate:r})=>{r(t).forEach(i=>an(e,i))});Q(Ce("@",Te(S("on:"))));p("on",N((e,{value:t,modifiers:r,expression:n},{cleanup:i})=>{let o=n?x(e,n):()=>{};e.tagName.toLowerCase()==="template"&&(e._x_forwardEvents||(e._x_forwardEvents=[]),e._x_forwardEvents.includes(t)||e._x_forwardEvents.push(t));let s=ie(e,t,r,a=>{o(()=>{},{scope:{$event:a},params:[a]})});i(()=>s())}));Je("Collapse","collapse","collapse");Je("Intersect","intersect","intersect");Je("Focus","trap","focus");Je("Mask","mask","mask");function Je(e,t,r){p(t,n=>T(`You can't use [x-${t}] without first installing the "${e}" plugin here: https://alpinejs.dev/plugins/${r}`,n))}F.setEvaluator(pt);F.setReactivityEngine({reactive:We,effect:jr,release:Fr,raw:g});var Ft=F;window.Alpine=Ft;queueMicrotask(()=>{Ft.start()});})(); diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 23963a6e1..9510a2392 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -18,10 +18,10 @@ href="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('favicon.ico') }}" type="image/x-icon"> - <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script> + <script src="{{ asset('plugins/alpinejs/3.12.0_cdn.min.js') }}"></script> {{-- <link rel="stylesheet" href="{{asset('css/adminlte.min.css')}}"> --}} - href="{{ asset('plugins/datatables/jquery.dataTables.min.css') }}"> + <link rel="stylesheet" href="{{ asset('plugins/datatables/jquery.dataTables.min.css') }}"> {{-- summernote --}} <link rel="stylesheet" href="{{ asset('plugins/summernote/summernote-bs4.min.css') }}"> From c1165c4561b419080c85630ffdf98bd03bd3996a Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sat, 29 Apr 2023 23:39:59 +0200 Subject: [PATCH 099/514] update sweetalert to local --- public/plugins/sweetalert2/sweetalert2.all.min.js | 4 ++-- themes/default/views/layouts/main.blade.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/plugins/sweetalert2/sweetalert2.all.min.js b/public/plugins/sweetalert2/sweetalert2.all.min.js index ba6370388..5f87e64c9 100644 --- a/public/plugins/sweetalert2/sweetalert2.all.min.js +++ b/public/plugins/sweetalert2/sweetalert2.all.min.js @@ -1,2 +1,2 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sweetalert2=e()}(this,function(){"use strict";function r(t){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function a(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function c(t,e,n){return e&&o(t.prototype,e),n&&o(t,n),t}function s(){return(s=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n,o=arguments[e];for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&(t[n]=o[n])}return t}).apply(this,arguments)}function u(t){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function l(t,e){return(l=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function d(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}function i(t,e,n){return(i=d()?Reflect.construct:function(t,e,n){var o=[null];o.push.apply(o,e);o=new(Function.bind.apply(t,o));return n&&l(o,n.prototype),o}).apply(null,arguments)}function p(t,e){return!e||"object"!=typeof e&&"function"!=typeof e?function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t):e}function f(t,e,n){return(f="undefined"!=typeof Reflect&&Reflect.get?Reflect.get:function(t,e,n){t=function(t,e){for(;!Object.prototype.hasOwnProperty.call(t,e)&&null!==(t=u(t)););return t}(t,e);if(t){e=Object.getOwnPropertyDescriptor(t,e);return e.get?e.get.call(n):e.value}})(t,e,n||t)}function m(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(e){return Object.keys(e).map(function(t){return e[t]})}function g(t){return Array.prototype.slice.call(t)}function v(t,e){e='"'.concat(t,'" is deprecated and will be removed in the next major release. Please use "').concat(e,'" instead.'),-1===Y.indexOf(e)&&(Y.push(e),W(e))}function b(t){return t&&"function"==typeof t.toPromise}function y(t){return b(t)?t.toPromise():Promise.resolve(t)}function w(t){return t&&Promise.resolve(t)===t}function C(t){return t instanceof Element||"object"===r(t=t)&&t.jquery}function k(){return document.body.querySelector(".".concat($.container))}function A(t){var e=k();return e?e.querySelector(t):null}function t(t){return A(".".concat(t))}function x(){return t($.popup)}function n(){var t=x();return g(t.querySelectorAll(".".concat($.icon)))}function B(){var t=n().filter(function(t){return wt(t)});return t.length?t[0]:null}function P(){return t($.title)}function E(){return t($.content)}function S(){return t($.image)}function O(){return t($["progress-steps"])}function T(){return t($["validation-message"])}function L(){return A(".".concat($.actions," .").concat($.confirm))}function q(){return A(".".concat($.actions," .").concat($.deny))}function D(){return A(".".concat($.loader))}function j(){return A(".".concat($.actions," .").concat($.cancel))}function I(){return t($.actions)}function M(){return t($.header)}function H(){return t($.footer)}function V(){return t($["timer-progress-bar"])}function R(){return t($.close)}function N(){var t=g(x().querySelectorAll('[tabindex]:not([tabindex="-1"]):not([tabindex="0"])')).sort(function(t,e){return t=parseInt(t.getAttribute("tabindex")),(e=parseInt(e.getAttribute("tabindex")))<t?1:t<e?-1:0}),e=g(x().querySelectorAll('\n a[href],\n area[href],\n input:not([disabled]),\n select:not([disabled]),\n textarea:not([disabled]),\n button:not([disabled]),\n iframe,\n object,\n embed,\n [tabindex="0"],\n [contenteditable],\n audio[controls],\n video[controls],\n summary\n')).filter(function(t){return"-1"!==t.getAttribute("tabindex")});return function(t){for(var e=[],n=0;n<t.length;n++)-1===e.indexOf(t[n])&&e.push(t[n]);return e}(t.concat(e)).filter(function(t){return wt(t)})}function U(){return!G()&&!document.body.classList.contains($["no-backdrop"])}function _(e,t){e.textContent="",t&&(t=(new DOMParser).parseFromString(t,"text/html"),g(t.querySelector("head").childNodes).forEach(function(t){e.appendChild(t)}),g(t.querySelector("body").childNodes).forEach(function(t){e.appendChild(t)}))}function F(t,e){if(e){for(var n=e.split(/\s+/),o=0;o<n.length;o++)if(!t.classList.contains(n[o]))return;return 1}}function z(t,e,n){var o,i;if(i=e,g((o=t).classList).forEach(function(t){-1===h($).indexOf(t)&&-1===h(X).indexOf(t)&&-1===h(i.showClass).indexOf(t)&&o.classList.remove(t)}),e.customClass&&e.customClass[n]){if("string"!=typeof e.customClass[n]&&!e.customClass[n].forEach)return W("Invalid type of customClass.".concat(n,'! Expected string or iterable object, got "').concat(r(e.customClass[n]),'"'));vt(t,e.customClass[n])}}var e="SweetAlert2:",W=function(t){console.warn("".concat(e," ").concat("object"===r(t)?t.join(" "):t))},K=function(t){console.error("".concat(e," ").concat(t))},Y=[],Z=function(t){return"function"==typeof t?t():t},Q=Object.freeze({cancel:"cancel",backdrop:"backdrop",close:"close",esc:"esc",timer:"timer"}),J=function(t){var e,n={};for(e in t)n[t[e]]="swal2-"+t[e];return n},$=J(["container","shown","height-auto","iosfix","popup","modal","no-backdrop","no-transition","toast","toast-shown","toast-column","show","hide","close","title","header","content","html-container","actions","confirm","deny","cancel","footer","icon","icon-content","image","input","file","range","select","radio","checkbox","label","textarea","inputerror","input-label","validation-message","progress-steps","active-progress-step","progress-step","progress-step-line","loader","loading","styled","top","top-start","top-end","top-left","top-right","center","center-start","center-end","center-left","center-right","bottom","bottom-start","bottom-end","bottom-left","bottom-right","grow-row","grow-column","grow-fullscreen","rtl","timer-progress-bar","timer-progress-bar-container","scrollbar-measure","icon-success","icon-warning","icon-info","icon-question","icon-error"]),X=J(["success","warning","info","question","error"]),G=function(){return document.body.classList.contains($["toast-shown"])},tt={previousBodyPadding:null};function et(t,e){if(!e)return null;switch(e){case"select":case"textarea":case"file":return yt(t,$[e]);case"checkbox":return t.querySelector(".".concat($.checkbox," input"));case"radio":return t.querySelector(".".concat($.radio," input:checked"))||t.querySelector(".".concat($.radio," input:first-child"));case"range":return t.querySelector(".".concat($.range," input"));default:return yt(t,$.input)}}function nt(t){var e;t.focus(),"file"!==t.type&&(e=t.value,t.value="",t.value=e)}function ot(t,e,n){t&&e&&("string"==typeof e&&(e=e.split(/\s+/).filter(Boolean)),e.forEach(function(e){t.forEach?t.forEach(function(t){n?t.classList.add(e):t.classList.remove(e)}):n?t.classList.add(e):t.classList.remove(e)}))}function it(t,e,n){n==="".concat(parseInt(n))&&(n=parseInt(n)),n||0===parseInt(n)?t.style[e]="number"==typeof n?"".concat(n,"px"):n:t.style.removeProperty(e)}function rt(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:"flex";t.style.display=e}function at(t){t.style.display="none"}function ct(t,e,n,o){(e=t.querySelector(e))&&(e.style[n]=o)}function st(t,e,n){e?rt(t,n):at(t)}function ut(t){return!!(t.scrollHeight>t.clientHeight)}function lt(t){var e=window.getComputedStyle(t),t=parseFloat(e.getPropertyValue("animation-duration")||"0"),e=parseFloat(e.getPropertyValue("transition-duration")||"0");return 0<t||0<e}function dt(t){var e=1<arguments.length&&void 0!==arguments[1]&&arguments[1],n=V();wt(n)&&(e&&(n.style.transition="none",n.style.width="100%"),setTimeout(function(){n.style.transition="width ".concat(t/1e3,"s linear"),n.style.width="0%"},10))}function pt(){return"undefined"==typeof window||"undefined"==typeof document}function ft(t){Mn.isVisible()&>!==t.target.value&&Mn.resetValidationMessage(),gt=t.target.value}function mt(t,e){t instanceof HTMLElement?e.appendChild(t):"object"===r(t)?At(t,e):t&&_(e,t)}function ht(t,e){var n,o,i,r,a=I(),c=D(),s=L(),u=q(),l=j();e.showConfirmButton||e.showDenyButton||e.showCancelButton||at(a),z(a,e,"actions"),Pt(s,"confirm",e),Pt(u,"deny",e),Pt(l,"cancel",e),n=s,o=u,i=l,(r=e).buttonsStyling?(vt([n,o,i],$.styled),r.confirmButtonColor&&(n.style.backgroundColor=r.confirmButtonColor),r.denyButtonColor&&(o.style.backgroundColor=r.denyButtonColor),r.cancelButtonColor&&(i.style.backgroundColor=r.cancelButtonColor)):bt([n,o,i],$.styled),e.reverseButtons&&(a.insertBefore(l,c),a.insertBefore(u,c),a.insertBefore(s,c)),_(c,e.loaderHtml),z(c,e,"loader")}var gt,vt=function(t,e){ot(t,e,!0)},bt=function(t,e){ot(t,e,!1)},yt=function(t,e){for(var n=0;n<t.childNodes.length;n++)if(F(t.childNodes[n],e))return t.childNodes[n]},wt=function(t){return!(!t||!(t.offsetWidth||t.offsetHeight||t.getClientRects().length))},Ct='\n <div aria-labelledby="'.concat($.title,'" aria-describedby="').concat($.content,'" class="').concat($.popup,'" tabindex="-1">\n <div class="').concat($.header,'">\n <ul class="').concat($["progress-steps"],'"></ul>\n <div class="').concat($.icon," ").concat(X.error,'"></div>\n <div class="').concat($.icon," ").concat(X.question,'"></div>\n <div class="').concat($.icon," ").concat(X.warning,'"></div>\n <div class="').concat($.icon," ").concat(X.info,'"></div>\n <div class="').concat($.icon," ").concat(X.success,'"></div>\n <img class="').concat($.image,'" />\n <h2 class="').concat($.title,'" id="').concat($.title,'"></h2>\n <button type="button" class="').concat($.close,'"></button>\n </div>\n <div class="').concat($.content,'">\n <div id="').concat($.content,'" class="').concat($["html-container"],'"></div>\n <input class="').concat($.input,'" />\n <input type="file" class="').concat($.file,'" />\n <div class="').concat($.range,'">\n <input type="range" />\n <output></output>\n </div>\n <select class="').concat($.select,'"></select>\n <div class="').concat($.radio,'"></div>\n <label for="').concat($.checkbox,'" class="').concat($.checkbox,'">\n <input type="checkbox" />\n <span class="').concat($.label,'"></span>\n </label>\n <textarea class="').concat($.textarea,'"></textarea>\n <div class="').concat($["validation-message"],'" id="').concat($["validation-message"],'"></div>\n </div>\n <div class="').concat($.actions,'">\n <div class="').concat($.loader,'"></div>\n <button type="button" class="').concat($.confirm,'"></button>\n <button type="button" class="').concat($.deny,'"></button>\n <button type="button" class="').concat($.cancel,'"></button>\n </div>\n <div class="').concat($.footer,'"></div>\n <div class="').concat($["timer-progress-bar-container"],'">\n <div class="').concat($["timer-progress-bar"],'"></div>\n </div>\n </div>\n').replace(/(^|\n)\s*/g,""),kt=function(t){var e,n,o,i,r,a=!!(i=k())&&(i.parentNode.removeChild(i),bt([document.documentElement,document.body],[$["no-backdrop"],$["toast-shown"],$["has-column"]]),!0);pt()?K("SweetAlert2 requires document to initialize"):((r=document.createElement("div")).className=$.container,a&&vt(r,$["no-transition"]),_(r,Ct),(i="string"==typeof(e=t.target)?document.querySelector(e):e).appendChild(r),a=t,(e=x()).setAttribute("role",a.toast?"alert":"dialog"),e.setAttribute("aria-live",a.toast?"polite":"assertive"),a.toast||e.setAttribute("aria-modal","true"),r=i,"rtl"===window.getComputedStyle(r).direction&&vt(k(),$.rtl),t=E(),a=yt(t,$.input),e=yt(t,$.file),n=t.querySelector(".".concat($.range," input")),o=t.querySelector(".".concat($.range," output")),i=yt(t,$.select),r=t.querySelector(".".concat($.checkbox," input")),t=yt(t,$.textarea),a.oninput=ft,e.onchange=ft,i.onchange=ft,r.onchange=ft,t.oninput=ft,n.oninput=function(t){ft(t),o.value=n.value},n.onchange=function(t){ft(t),n.nextSibling.value=n.value})},At=function(t,e){t.jquery?xt(e,t):_(e,t.toString())},xt=function(t,e){if(t.textContent="",0 in e)for(var n=0;n in e;n++)t.appendChild(e[n].cloneNode(!0));else t.appendChild(e.cloneNode(!0))},Bt=function(){if(pt())return!1;var t,e=document.createElement("div"),n={WebkitAnimation:"webkitAnimationEnd",OAnimation:"oAnimationEnd oanimationend",animation:"animationend"};for(t in n)if(Object.prototype.hasOwnProperty.call(n,t)&&void 0!==e.style[t])return n[t];return!1}();function Pt(t,e,n){st(t,n["show".concat(m(e),"Button")],"inline-block"),_(t,n["".concat(e,"ButtonText")]),t.setAttribute("aria-label",n["".concat(e,"ButtonAriaLabel")]),t.className=$[e],z(t,n,"".concat(e,"Button")),vt(t,n["".concat(e,"ButtonClass")])}function Et(t,e){var n,o,i=k();i&&(o=i,"string"==typeof(n=e.backdrop)?o.style.background=n:n||vt([document.documentElement,document.body],$["no-backdrop"]),!e.backdrop&&e.allowOutsideClick&&W('"allowOutsideClick" parameter requires `backdrop` parameter to be set to `true`'),o=i,(n=e.position)in $?vt(o,$[n]):(W('The "position" parameter is not valid, defaulting to "center"'),vt(o,$.center)),n=i,!(o=e.grow)||"string"!=typeof o||(o="grow-".concat(o))in $&&vt(n,$[o]),z(i,e,"container"),(e=document.body.getAttribute("data-swal2-queue-step"))&&(i.setAttribute("data-queue-step",e),document.body.removeAttribute("data-swal2-queue-step")))}function St(t,e){t.placeholder&&!e.inputPlaceholder||(t.placeholder=e.inputPlaceholder)}function Ot(t,e,n){var o,i;n.inputLabel&&(t.id=$.input,o=document.createElement("label"),i=$["input-label"],o.setAttribute("for",t.id),o.className=i,vt(o,n.customClass.inputLabel),o.innerText=n.inputLabel,e.insertAdjacentElement("beforebegin",o))}var Tt={promise:new WeakMap,innerParams:new WeakMap,domCache:new WeakMap},Lt=["input","file","range","select","radio","checkbox","textarea"],qt=function(t){if(!Mt[t.input])return K('Unexpected type of input! Expected "text", "email", "password", "number", "tel", "select", "radio", "checkbox", "textarea", "file" or "url", got "'.concat(t.input,'"'));var e=It(t.input),n=Mt[t.input](e,t);rt(n),setTimeout(function(){nt(n)})},Dt=function(t,e){var n=et(E(),t);if(n)for(var o in!function(t){for(var e=0;e<t.attributes.length;e++){var n=t.attributes[e].name;-1===["type","value","style"].indexOf(n)&&t.removeAttribute(n)}}(n),e)"range"===t&&"placeholder"===o||n.setAttribute(o,e[o])},jt=function(t){var e=It(t.input);t.customClass&&vt(e,t.customClass.input)},It=function(t){t=$[t]||$.input;return yt(E(),t)},Mt={};Mt.text=Mt.email=Mt.password=Mt.number=Mt.tel=Mt.url=function(t,e){return"string"==typeof e.inputValue||"number"==typeof e.inputValue?t.value=e.inputValue:w(e.inputValue)||W('Unexpected type of inputValue! Expected "string", "number" or "Promise", got "'.concat(r(e.inputValue),'"')),Ot(t,t,e),St(t,e),t.type=e.input,t},Mt.file=function(t,e){return Ot(t,t,e),St(t,e),t},Mt.range=function(t,e){var n=t.querySelector("input"),o=t.querySelector("output");return n.value=e.inputValue,n.type=e.input,o.value=e.inputValue,Ot(n,t,e),t},Mt.select=function(t,e){var n;return t.textContent="",e.inputPlaceholder&&(n=document.createElement("option"),_(n,e.inputPlaceholder),n.value="",n.disabled=!0,n.selected=!0,t.appendChild(n)),Ot(t,t,e),t},Mt.radio=function(t){return t.textContent="",t},Mt.checkbox=function(t,e){var n=et(E(),"checkbox");n.value=1,n.id=$.checkbox,n.checked=Boolean(e.inputValue);n=t.querySelector("span");return _(n,e.inputPlaceholder),t},Mt.textarea=function(e,t){e.value=t.inputValue,St(e,t),Ot(e,e,t);function n(t){return parseInt(window.getComputedStyle(t).paddingLeft)+parseInt(window.getComputedStyle(t).paddingRight)}var o;return"MutationObserver"in window&&(o=parseInt(window.getComputedStyle(x()).width),new MutationObserver(function(){var t=e.offsetWidth+n(x())+n(E());x().style.width=o<t?"".concat(t,"px"):null}).observe(e,{attributes:!0,attributeFilter:["style"]})),e};function Ht(t,e){var o,i,r,n=E().querySelector("#".concat($.content));e.html?(mt(e.html,n),rt(n,"block")):e.text?(n.textContent=e.text,rt(n,"block")):at(n),t=t,o=e,i=E(),t=Tt.innerParams.get(t),r=!t||o.input!==t.input,Lt.forEach(function(t){var e=$[t],n=yt(i,e);Dt(t,o.inputAttributes),n.className=e,r&&at(n)}),o.input&&(r&&qt(o),jt(o)),z(E(),e,"content")}function Vt(){return k()&&k().getAttribute("data-queue-step")}function Rt(t,o){var i=O();if(!o.progressSteps||0===o.progressSteps.length)return at(i),0;rt(i),i.textContent="";var r=parseInt(void 0===o.currentProgressStep?Vt():o.currentProgressStep);r>=o.progressSteps.length&&W("Invalid currentProgressStep parameter, it should be less than progressSteps.length (currentProgressStep like JS arrays starts from 0)"),o.progressSteps.forEach(function(t,e){var n,t=(n=t,t=document.createElement("li"),vt(t,$["progress-step"]),_(t,n),t);i.appendChild(t),e===r&&vt(t,$["active-progress-step"]),e!==o.progressSteps.length-1&&(t=o,e=document.createElement("li"),vt(e,$["progress-step-line"]),t.progressStepsDistance&&(e.style.width=t.progressStepsDistance),e=e,i.appendChild(e))})}function Nt(t,e){var n=M();z(n,e,"header"),Rt(0,e),n=t,t=e,(n=Tt.innerParams.get(n))&&t.icon===n.icon&&B()?zt(B(),t):(Ft(),t.icon&&(-1!==Object.keys(X).indexOf(t.icon)?(n=A(".".concat($.icon,".").concat(X[t.icon])),rt(n),Kt(n,t),zt(n,t),vt(n,t.showClass.icon)):K('Unknown icon! Expected "success", "error", "warning", "info" or "question", got "'.concat(t.icon,'"')))),function(t){var e=S();if(!t.imageUrl)return at(e);rt(e,""),e.setAttribute("src",t.imageUrl),e.setAttribute("alt",t.imageAlt),it(e,"width",t.imageWidth),it(e,"height",t.imageHeight),e.className=$.image,z(e,t,"image")}(e),n=e,t=P(),st(t,n.title||n.titleText),n.title&&mt(n.title,t),n.titleText&&(t.innerText=n.titleText),z(t,n,"title"),n=e,e=R(),_(e,n.closeButtonHtml),z(e,n,"closeButton"),st(e,n.showCloseButton),e.setAttribute("aria-label",n.closeButtonAriaLabel)}function Ut(t,e){var n,o;o=e,n=x(),it(n,"width",o.width),it(n,"padding",o.padding),o.background&&(n.style.background=o.background),Jt(n,o),Et(0,e),Nt(t,e),Ht(t,e),ht(0,e),o=e,t=H(),st(t,o.footer),o.footer&&mt(o.footer,t),z(t,o,"footer"),"function"==typeof e.didRender?e.didRender(x()):"function"==typeof e.onRender&&e.onRender(x())}function _t(){return L()&&L().click()}var Ft=function(){for(var t=n(),e=0;e<t.length;e++)at(t[e])},zt=function(t,e){Yt(t,e),Wt(),z(t,e,"icon")},Wt=function(){for(var t=x(),e=window.getComputedStyle(t).getPropertyValue("background-color"),n=t.querySelectorAll("[class^=swal2-success-circular-line], .swal2-success-fix"),o=0;o<n.length;o++)n[o].style.backgroundColor=e},Kt=function(t,e){t.textContent="",e.iconHtml?_(t,Zt(e.iconHtml)):"success"===e.icon?_(t,'\n <div class="swal2-success-circular-line-left"></div>\n <span class="swal2-success-line-tip"></span> <span class="swal2-success-line-long"></span>\n <div class="swal2-success-ring"></div> <div class="swal2-success-fix"></div>\n <div class="swal2-success-circular-line-right"></div>\n '):"error"===e.icon?_(t,'\n <span class="swal2-x-mark">\n <span class="swal2-x-mark-line-left"></span>\n <span class="swal2-x-mark-line-right"></span>\n </span>\n '):_(t,Zt({question:"?",warning:"!",info:"i"}[e.icon]))},Yt=function(t,e){if(e.iconColor){t.style.color=e.iconColor,t.style.borderColor=e.iconColor;for(var n=0,o=[".swal2-success-line-tip",".swal2-success-line-long",".swal2-x-mark-line-left",".swal2-x-mark-line-right"];n<o.length;n++)ct(t,o[n],"backgroundColor",e.iconColor);ct(t,".swal2-success-ring","borderColor",e.iconColor)}},Zt=function(t){return'<div class="'.concat($["icon-content"],'">').concat(t,"</div>")},Qt=[],Jt=function(t,e){t.className="".concat($.popup," ").concat(wt(t)?e.showClass.popup:""),e.toast?(vt([document.documentElement,document.body],$["toast-shown"]),vt(t,$.toast)):vt(t,$.modal),z(t,e,"popup"),"string"==typeof e.customClass&&vt(t,e.customClass),e.icon&&vt(t,$["icon-".concat(e.icon)])};function $t(t){var e=x();e||Mn.fire(),e=x();var n=I(),o=D();!t&&wt(L())&&(t=L()),rt(n),t&&(at(t),o.setAttribute("data-button-to-replace",t.className)),o.parentNode.insertBefore(o,t),vt([e,n],$.loading),rt(o),e.setAttribute("data-loading",!0),e.setAttribute("aria-busy",!0),e.focus()}function Xt(){return new Promise(function(t){var e=window.scrollX,n=window.scrollY;ee.restoreFocusTimeout=setTimeout(function(){ee.previousActiveElement&&ee.previousActiveElement.focus?(ee.previousActiveElement.focus(),ee.previousActiveElement=null):document.body&&document.body.focus(),t()},100),void 0!==e&&void 0!==n&&window.scrollTo(e,n)})}function Gt(){if(ee.timeout)return function(){var t=V(),e=parseInt(window.getComputedStyle(t).width);t.style.removeProperty("transition"),t.style.width="100%";var n=parseInt(window.getComputedStyle(t).width),n=parseInt(e/n*100);t.style.removeProperty("transition"),t.style.width="".concat(n,"%")}(),ee.timeout.stop()}function te(){if(ee.timeout){var t=ee.timeout.start();return dt(t),t}}var ee={},ne=!1,oe={};function ie(t){for(var e=t.target;e&&e!==document;e=e.parentNode)for(var n in oe){var o=e.getAttribute(n);if(o)return void oe[n].fire({template:o})}}function re(t){return Object.prototype.hasOwnProperty.call(se,t)}function ae(t){return le[t]}function ce(t){for(var e in t)re(o=e)||W('Unknown parameter "'.concat(o,'"')),t.toast&&(n=e,-1!==de.indexOf(n)&&W('The parameter "'.concat(n,'" is incompatible with toasts'))),ae(n=e)&&v(n,ae(n));var n,o}var se={title:"",titleText:"",text:"",html:"",footer:"",icon:void 0,iconColor:void 0,iconHtml:void 0,template:void 0,toast:!1,animation:!0,showClass:{popup:"swal2-show",backdrop:"swal2-backdrop-show",icon:"swal2-icon-show"},hideClass:{popup:"swal2-hide",backdrop:"swal2-backdrop-hide",icon:"swal2-icon-hide"},customClass:{},target:"body",backdrop:!0,heightAuto:!0,allowOutsideClick:!0,allowEscapeKey:!0,allowEnterKey:!0,stopKeydownPropagation:!0,keydownListenerCapture:!1,showConfirmButton:!0,showDenyButton:!1,showCancelButton:!1,preConfirm:void 0,preDeny:void 0,confirmButtonText:"OK",confirmButtonAriaLabel:"",confirmButtonColor:void 0,denyButtonText:"No",denyButtonAriaLabel:"",denyButtonColor:void 0,cancelButtonText:"Cancel",cancelButtonAriaLabel:"",cancelButtonColor:void 0,buttonsStyling:!0,reverseButtons:!1,focusConfirm:!0,focusDeny:!1,focusCancel:!1,showCloseButton:!1,closeButtonHtml:"×",closeButtonAriaLabel:"Close this dialog",loaderHtml:"",showLoaderOnConfirm:!1,imageUrl:void 0,imageWidth:void 0,imageHeight:void 0,imageAlt:"",timer:void 0,timerProgressBar:!1,width:void 0,padding:void 0,background:void 0,input:void 0,inputPlaceholder:"",inputLabel:"",inputValue:"",inputOptions:{},inputAutoTrim:!0,inputAttributes:{},inputValidator:void 0,returnInputValueOnDeny:!1,validationMessage:void 0,grow:!1,position:"center",progressSteps:[],currentProgressStep:void 0,progressStepsDistance:void 0,onBeforeOpen:void 0,onOpen:void 0,willOpen:void 0,didOpen:void 0,onRender:void 0,didRender:void 0,onClose:void 0,onAfterClose:void 0,willClose:void 0,didClose:void 0,onDestroy:void 0,didDestroy:void 0,scrollbarPadding:!0},ue=["allowEscapeKey","allowOutsideClick","background","buttonsStyling","cancelButtonAriaLabel","cancelButtonColor","cancelButtonText","closeButtonAriaLabel","closeButtonHtml","confirmButtonAriaLabel","confirmButtonColor","confirmButtonText","currentProgressStep","customClass","denyButtonAriaLabel","denyButtonColor","denyButtonText","didClose","didDestroy","footer","hideClass","html","icon","iconColor","imageAlt","imageHeight","imageUrl","imageWidth","onAfterClose","onClose","onDestroy","progressSteps","reverseButtons","showCancelButton","showCloseButton","showConfirmButton","showDenyButton","text","title","titleText","willClose"],le={animation:'showClass" and "hideClass',onBeforeOpen:"willOpen",onOpen:"didOpen",onRender:"didRender",onClose:"willClose",onAfterClose:"didClose",onDestroy:"didDestroy"},de=["allowOutsideClick","allowEnterKey","backdrop","focusConfirm","focusDeny","focusCancel","heightAuto","keydownListenerCapture"],pe=Object.freeze({isValidParameter:re,isUpdatableParameter:function(t){return-1!==ue.indexOf(t)},isDeprecatedParameter:ae,argsToParams:function(n){var o={};return"object"!==r(n[0])||C(n[0])?["title","html","icon"].forEach(function(t,e){e=n[e];"string"==typeof e||C(e)?o[t]=e:void 0!==e&&K("Unexpected type of ".concat(t,'! Expected "string" or "Element", got ').concat(r(e)))}):s(o,n[0]),o},isVisible:function(){return wt(x())},clickConfirm:_t,clickDeny:function(){return q()&&q().click()},clickCancel:function(){return j()&&j().click()},getContainer:k,getPopup:x,getTitle:P,getContent:E,getHtmlContainer:function(){return t($["html-container"])},getImage:S,getIcon:B,getIcons:n,getInputLabel:function(){return t($["input-label"])},getCloseButton:R,getActions:I,getConfirmButton:L,getDenyButton:q,getCancelButton:j,getLoader:D,getHeader:M,getFooter:H,getTimerProgressBar:V,getFocusableElements:N,getValidationMessage:T,isLoading:function(){return x().hasAttribute("data-loading")},fire:function(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];return i(this,e)},mixin:function(r){return function(t){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&l(t,e)}(i,t);var n,o,e=(n=i,o=d(),function(){var t,e=u(n);return p(this,o?(t=u(this).constructor,Reflect.construct(e,arguments,t)):e.apply(this,arguments))});function i(){return a(this,i),e.apply(this,arguments)}return c(i,[{key:"_main",value:function(t,e){return f(u(i.prototype),"_main",this).call(this,t,s({},e,r))}}]),i}(this)},queue:function(t){var r=this;Qt=t;function a(t,e){Qt=[],t(e)}var c=[];return new Promise(function(i){!function e(n,o){n<Qt.length?(document.body.setAttribute("data-swal2-queue-step",n),r.fire(Qt[n]).then(function(t){void 0!==t.value?(c.push(t.value),e(n+1,o)):a(i,{dismiss:t.dismiss})})):a(i,{value:c})}(0)})},getQueueStep:Vt,insertQueueStep:function(t,e){return e&&e<Qt.length?Qt.splice(e,0,t):Qt.push(t)},deleteQueueStep:function(t){void 0!==Qt[t]&&Qt.splice(t,1)},showLoading:$t,enableLoading:$t,getTimerLeft:function(){return ee.timeout&&ee.timeout.getTimerLeft()},stopTimer:Gt,resumeTimer:te,toggleTimer:function(){var t=ee.timeout;return t&&(t.running?Gt:te)()},increaseTimer:function(t){if(ee.timeout){t=ee.timeout.increase(t);return dt(t,!0),t}},isTimerRunning:function(){return ee.timeout&&ee.timeout.isRunning()},bindClickHandler:function(){oe[0<arguments.length&&void 0!==arguments[0]?arguments[0]:"data-swal-template"]=this,ne||(document.body.addEventListener("click",ie),ne=!0)}});function fe(){var t,e;Tt.innerParams.get(this)&&(t=Tt.domCache.get(this),at(t.loader),(e=t.popup.getElementsByClassName(t.loader.getAttribute("data-button-to-replace"))).length?rt(e[0],"inline-block"):wt(L())||wt(q())||wt(j())||at(t.actions),bt([t.popup,t.actions],$.loading),t.popup.removeAttribute("aria-busy"),t.popup.removeAttribute("data-loading"),t.confirmButton.disabled=!1,t.denyButton.disabled=!1,t.cancelButton.disabled=!1)}function me(){null===tt.previousBodyPadding&&document.body.scrollHeight>window.innerHeight&&(tt.previousBodyPadding=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right")),document.body.style.paddingRight="".concat(tt.previousBodyPadding+function(){var t=document.createElement("div");t.className=$["scrollbar-measure"],document.body.appendChild(t);var e=t.getBoundingClientRect().width-t.clientWidth;return document.body.removeChild(t),e}(),"px"))}function he(){return!!window.MSInputMethodContext&&!!document.documentMode}function ge(){var t=k(),e=x();t.style.removeProperty("align-items"),e.offsetTop<0&&(t.style.alignItems="flex-start")}var ve=function(){navigator.userAgent.match(/(CriOS|FxiOS|EdgiOS|YaBrowser|UCBrowser)/i)||x().scrollHeight>window.innerHeight-44&&(k().style.paddingBottom="".concat(44,"px"))},be=function(){var e,t=k();t.ontouchstart=function(t){e=ye(t)},t.ontouchmove=function(t){e&&(t.preventDefault(),t.stopPropagation())}},ye=function(t){var e=t.target,n=k();return!we(t)&&!Ce(t)&&(e===n||!(ut(n)||"INPUT"===e.tagName||ut(E())&&E().contains(e)))},we=function(t){return t.touches&&t.touches.length&&"stylus"===t.touches[0].touchType},Ce=function(t){return t.touches&&1<t.touches.length},ke={swalPromiseResolve:new WeakMap};function Ae(t,e,n,o){n?Oe(t,o):(Xt().then(function(){return Oe(t,o)}),ee.keydownTarget.removeEventListener("keydown",ee.keydownHandler,{capture:ee.keydownListenerCapture}),ee.keydownHandlerAdded=!1),e.parentNode&&!document.body.getAttribute("data-swal2-queue-step")&&e.parentNode.removeChild(e),U()&&(null!==tt.previousBodyPadding&&(document.body.style.paddingRight="".concat(tt.previousBodyPadding,"px"),tt.previousBodyPadding=null),F(document.body,$.iosfix)&&(e=parseInt(document.body.style.top,10),bt(document.body,$.iosfix),document.body.style.top="",document.body.scrollTop=-1*e),"undefined"!=typeof window&&he()&&window.removeEventListener("resize",ge),g(document.body.children).forEach(function(t){t.hasAttribute("data-previous-aria-hidden")?(t.setAttribute("aria-hidden",t.getAttribute("data-previous-aria-hidden")),t.removeAttribute("data-previous-aria-hidden")):t.removeAttribute("aria-hidden")})),bt([document.documentElement,document.body],[$.shown,$["height-auto"],$["no-backdrop"],$["toast-shown"],$["toast-column"]])}function xe(t){var e,n,o,i=x();i&&(t=Be(t),(e=Tt.innerParams.get(this))&&!F(i,e.hideClass.popup)&&(n=ke.swalPromiseResolve.get(this),bt(i,e.showClass.popup),vt(i,e.hideClass.popup),o=k(),bt(o,e.showClass.backdrop),vt(o,e.hideClass.backdrop),Pe(this,i,e),n(t)))}function Be(t){return void 0===t?{isConfirmed:!1,isDenied:!1,isDismissed:!0}:s({isConfirmed:!1,isDenied:!1,isDismissed:!1},t)}function Pe(t,e,n){var o=k(),i=Bt&<(e),r=n.onClose,a=n.onAfterClose,c=n.willClose,n=n.didClose;Ee(e,c,r),i?Se(t,e,o,n||a):Ae(t,o,G(),n||a)}var Ee=function(t,e,n){null!==e&&"function"==typeof e?e(t):null!==n&&"function"==typeof n&&n(t)},Se=function(t,e,n,o){ee.swalCloseEventFinishedCallback=Ae.bind(null,t,n,G(),o),e.addEventListener(Bt,function(t){t.target===e&&(ee.swalCloseEventFinishedCallback(),delete ee.swalCloseEventFinishedCallback)})},Oe=function(t,e){setTimeout(function(){"function"==typeof e&&e(),t._destroy()})};function Te(t,e,n){var o=Tt.domCache.get(t);e.forEach(function(t){o[t].disabled=n})}function Le(t,e){if(!t)return!1;if("radio"===t.type)for(var n=t.parentNode.parentNode.querySelectorAll("input"),o=0;o<n.length;o++)n[o].disabled=e;else t.disabled=e}var qe=function(){function n(t,e){a(this,n),this.callback=t,this.remaining=e,this.running=!1,this.start()}return c(n,[{key:"start",value:function(){return this.running||(this.running=!0,this.started=new Date,this.id=setTimeout(this.callback,this.remaining)),this.remaining}},{key:"stop",value:function(){return this.running&&(this.running=!1,clearTimeout(this.id),this.remaining-=new Date-this.started),this.remaining}},{key:"increase",value:function(t){var e=this.running;return e&&this.stop(),this.remaining+=t,e&&this.start(),this.remaining}},{key:"getTimerLeft",value:function(){return this.running&&(this.stop(),this.start()),this.remaining}},{key:"isRunning",value:function(){return this.running}}]),n}(),De={email:function(t,e){return/^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,24}$/.test(t)?Promise.resolve():Promise.resolve(e||"Invalid email address")},url:function(t,e){return/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)$/.test(t)?Promise.resolve():Promise.resolve(e||"Invalid URL")}};function je(t){var e,n;(e=t).inputValidator||Object.keys(De).forEach(function(t){e.input===t&&(e.inputValidator=De[t])}),t.showLoaderOnConfirm&&!t.preConfirm&&W("showLoaderOnConfirm is set to true, but preConfirm is not defined.\nshowLoaderOnConfirm should be used together with preConfirm, see usage example:\nhttps://sweetalert2.github.io/#ajax-request"),t.animation=Z(t.animation),(n=t).target&&("string"!=typeof n.target||document.querySelector(n.target))&&("string"==typeof n.target||n.target.appendChild)||(W('Target parameter is not valid, defaulting to "body"'),n.target="body"),"string"==typeof t.title&&(t.title=t.title.split("\n").join("<br />")),kt(t)}function Ie(t){var e=k(),n=x();"function"==typeof t.willOpen?t.willOpen(n):"function"==typeof t.onBeforeOpen&&t.onBeforeOpen(n);var o=window.getComputedStyle(document.body).overflowY;$e(e,n,t),setTimeout(function(){Qe(e,n)},10),U()&&(Je(e,t.scrollbarPadding,o),g(document.body.children).forEach(function(t){t===k()||function(t,e){if("function"==typeof t.contains)return t.contains(e)}(t,k())||(t.hasAttribute("aria-hidden")&&t.setAttribute("data-previous-aria-hidden",t.getAttribute("aria-hidden")),t.setAttribute("aria-hidden","true"))})),G()||ee.previousActiveElement||(ee.previousActiveElement=document.activeElement),Ze(n,t),bt(e,$["no-transition"])}function Me(t){var e=x();t.target===e&&(t=k(),e.removeEventListener(Bt,Me),t.style.overflowY="auto")}function He(t,e){t.closePopup({isConfirmed:!0,value:e})}function Ve(t,e,n){var o=N();if(o.length)return(e+=n)===o.length?e=0:-1===e&&(e=o.length-1),o[e].focus();x().focus()}var Re=["swal-title","swal-html","swal-footer"],Ne=function(t){var n={};return g(t.querySelectorAll("swal-param")).forEach(function(t){Ye(t,["name","value"]);var e=t.getAttribute("name"),t=t.getAttribute("value");"boolean"==typeof se[e]&&"false"===t&&(t=!1),"object"===r(se[e])&&(t=JSON.parse(t)),n[e]=t}),n},Ue=function(t){var n={};return g(t.querySelectorAll("swal-button")).forEach(function(t){Ye(t,["type","color","aria-label"]);var e=t.getAttribute("type");n["".concat(e,"ButtonText")]=t.innerHTML,n["show".concat(m(e),"Button")]=!0,t.hasAttribute("color")&&(n["".concat(e,"ButtonColor")]=t.getAttribute("color")),t.hasAttribute("aria-label")&&(n["".concat(e,"ButtonAriaLabel")]=t.getAttribute("aria-label"))}),n},_e=function(t){var e={},t=t.querySelector("swal-image");return t&&(Ye(t,["src","width","height","alt"]),t.hasAttribute("src")&&(e.imageUrl=t.getAttribute("src")),t.hasAttribute("width")&&(e.imageWidth=t.getAttribute("width")),t.hasAttribute("height")&&(e.imageHeight=t.getAttribute("height")),t.hasAttribute("alt")&&(e.imageAlt=t.getAttribute("alt"))),e},Fe=function(t){var e={},t=t.querySelector("swal-icon");return t&&(Ye(t,["type","color"]),t.hasAttribute("type")&&(e.icon=t.getAttribute("type")),t.hasAttribute("color")&&(e.iconColor=t.getAttribute("color")),e.iconHtml=t.innerHTML),e},ze=function(t){var n={},e=t.querySelector("swal-input");e&&(Ye(e,["type","label","placeholder","value"]),n.input=e.getAttribute("type")||"text",e.hasAttribute("label")&&(n.inputLabel=e.getAttribute("label")),e.hasAttribute("placeholder")&&(n.inputPlaceholder=e.getAttribute("placeholder")),e.hasAttribute("value")&&(n.inputValue=e.getAttribute("value")));t=t.querySelectorAll("swal-input-option");return t.length&&(n.inputOptions={},g(t).forEach(function(t){Ye(t,["value"]);var e=t.getAttribute("value"),t=t.innerHTML;n.inputOptions[e]=t})),n},We=function(t,e){var n,o={};for(n in e){var i=e[n],r=t.querySelector(i);r&&(Ye(r,[]),o[i.replace(/^swal-/,"")]=r.innerHTML)}return o},Ke=function(t){var e=Re.concat(["swal-param","swal-button","swal-image","swal-icon","swal-input","swal-input-option"]);g(t.children).forEach(function(t){t=t.tagName.toLowerCase();-1===e.indexOf(t)&&W("Unrecognized element <".concat(t,">"))})},Ye=function(e,n){g(e.attributes).forEach(function(t){-1===n.indexOf(t.name)&&W(['Unrecognized attribute "'.concat(t.name,'" on <').concat(e.tagName.toLowerCase(),">."),"".concat(n.length?"Allowed attributes are: ".concat(n.join(", ")):"To set the value, use HTML within the element.")])})},Ze=function(t,e){"function"==typeof e.didOpen?setTimeout(function(){return e.didOpen(t)}):"function"==typeof e.onOpen&&setTimeout(function(){return e.onOpen(t)})},Qe=function(t,e){Bt&<(e)?(t.style.overflowY="hidden",e.addEventListener(Bt,Me)):t.style.overflowY="auto"},Je=function(t,e,n){var o;(/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream||"MacIntel"===navigator.platform&&1<navigator.maxTouchPoints)&&!F(document.body,$.iosfix)&&(o=document.body.scrollTop,document.body.style.top="".concat(-1*o,"px"),vt(document.body,$.iosfix),be(),ve()),"undefined"!=typeof window&&he()&&(ge(),window.addEventListener("resize",ge)),e&&"hidden"!==n&&me(),setTimeout(function(){t.scrollTop=0})},$e=function(t,e,n){vt(t,n.showClass.backdrop),e.style.setProperty("opacity","0","important"),rt(e),setTimeout(function(){vt(e,n.showClass.popup),e.style.removeProperty("opacity")},10),vt([document.documentElement,document.body],$.shown),n.heightAuto&&n.backdrop&&!n.toast&&vt([document.documentElement,document.body],$["height-auto"])},Xe=function(t){return t.checked?1:0},Ge=function(t){return t.checked?t.value:null},tn=function(t){return t.files.length?null!==t.getAttribute("multiple")?t.files:t.files[0]:null},en=function(e,n){function o(t){return on[n.input](i,rn(t),n)}var i=E();b(n.inputOptions)||w(n.inputOptions)?($t(),y(n.inputOptions).then(function(t){e.hideLoading(),o(t)})):"object"===r(n.inputOptions)?o(n.inputOptions):K("Unexpected type of inputOptions! Expected object, Map or Promise, got ".concat(r(n.inputOptions)))},nn=function(e,n){var o=e.getInput();at(o),y(n.inputValue).then(function(t){o.value="number"===n.input?parseFloat(t)||0:"".concat(t),rt(o),o.focus(),e.hideLoading()}).catch(function(t){K("Error in inputValue promise: ".concat(t)),o.value="",rt(o),o.focus(),e.hideLoading()})},on={select:function(t,e,i){function o(t,e,n){var o=document.createElement("option");o.value=n,_(o,e),o.selected=an(n,i.inputValue),t.appendChild(o)}var r=yt(t,$.select);e.forEach(function(t){var e,n=t[0],t=t[1];Array.isArray(t)?((e=document.createElement("optgroup")).label=n,e.disabled=!1,r.appendChild(e),t.forEach(function(t){return o(e,t[1],t[0])})):o(r,t,n)}),r.focus()},radio:function(t,e,i){var r=yt(t,$.radio);e.forEach(function(t){var e=t[0],n=t[1],o=document.createElement("input"),t=document.createElement("label");o.type="radio",o.name=$.radio,o.value=e,an(e,i.inputValue)&&(o.checked=!0);e=document.createElement("span");_(e,n),e.className=$.label,t.appendChild(o),t.appendChild(e),r.appendChild(t)});e=r.querySelectorAll("input");e.length&&e[0].focus()}},rn=function n(o){var i=[];return"undefined"!=typeof Map&&o instanceof Map?o.forEach(function(t,e){"object"===r(t)&&(t=n(t)),i.push([e,t])}):Object.keys(o).forEach(function(t){var e=o[t];"object"===r(e)&&(e=n(e)),i.push([t,e])}),i},an=function(t,e){return e&&e.toString()===t.toString()},cn=function(t,e,n){var o=function(t,e){var n=t.getInput();if(!n)return null;switch(e.input){case"checkbox":return Xe(n);case"radio":return Ge(n);case"file":return tn(n);default:return e.inputAutoTrim?n.value.trim():n.value}}(t,e);e.inputValidator?sn(t,e,o):t.getInput().checkValidity()?("deny"===n?un:ln)(t,e,o):(t.enableButtons(),t.showValidationMessage(e.validationMessage))},sn=function(e,n,o){e.disableInput(),Promise.resolve().then(function(){return y(n.inputValidator(o,n.validationMessage))}).then(function(t){e.enableButtons(),e.enableInput(),t?e.showValidationMessage(t):ln(e,n,o)})},un=function(e,t,n){t.preDeny?Promise.resolve().then(function(){return y(t.preDeny(n,t.validationMessage))}).then(function(t){!1===t?e.hideLoading():e.closePopup({isDenied:!0,value:void 0===t?n:t})}):e.closePopup({isDenied:!0,value:n})},ln=function(e,t,n){t.showLoaderOnConfirm&&$t(),t.preConfirm?(e.resetValidationMessage(),Promise.resolve().then(function(){return y(t.preConfirm(n,t.validationMessage))}).then(function(t){wt(T())||!1===t?e.hideLoading():He(e,void 0===t?n:t)})):He(e,n)},dn=["ArrowRight","ArrowDown","Right","Down"],pn=["ArrowLeft","ArrowUp","Left","Up"],fn=["Escape","Esc"],mn=function(t,e,n){var o=Tt.innerParams.get(t);o.stopKeydownPropagation&&e.stopPropagation(),"Enter"===e.key?hn(t,e,o):"Tab"===e.key?gn(e,o):-1!==[].concat(dn,pn).indexOf(e.key)?vn(e.key):-1!==fn.indexOf(e.key)&&bn(e,o,n)},hn=function(t,e,n){e.isComposing||e.target&&t.getInput()&&e.target.outerHTML===t.getInput().outerHTML&&-1===["textarea","file"].indexOf(n.input)&&(_t(),e.preventDefault())},gn=function(t,e){for(var n=t.target,o=N(),i=-1,r=0;r<o.length;r++)if(n===o[r]){i=r;break}t.shiftKey?Ve(0,i,-1):Ve(0,i,1),t.stopPropagation(),t.preventDefault()},vn=function(t){-1!==[L(),q(),j()].indexOf(document.activeElement)&&(t=-1!==dn.indexOf(t)?"nextElementSibling":"previousElementSibling",(t=document.activeElement[t])&&t.focus())},bn=function(t,e,n){Z(e.allowEscapeKey)&&(t.preventDefault(),n(Q.esc))},yn=function(e,t,n){t.popup.onclick=function(){var t=Tt.innerParams.get(e);t.showConfirmButton||t.showDenyButton||t.showCancelButton||t.showCloseButton||t.timer||t.input||n(Q.close)}},wn=!1,Cn=function(e){e.popup.onmousedown=function(){e.container.onmouseup=function(t){e.container.onmouseup=void 0,t.target===e.container&&(wn=!0)}}},kn=function(e){e.container.onmousedown=function(){e.popup.onmouseup=function(t){e.popup.onmouseup=void 0,t.target!==e.popup&&!e.popup.contains(t.target)||(wn=!0)}}},An=function(n,o,i){o.container.onclick=function(t){var e=Tt.innerParams.get(n);wn?wn=!1:t.target===o.container&&Z(e.allowOutsideClick)&&i(Q.backdrop)}};function xn(t,e){var n=function(t){t="string"==typeof t.template?document.querySelector(t.template):t.template;if(!t)return{};t=t.content||t;return Ke(t),s(Ne(t),Ue(t),_e(t),Fe(t),ze(t),We(t,Re))}(t),o=s({},se.showClass,e.showClass,n.showClass,t.showClass),i=s({},se.hideClass,e.hideClass,n.hideClass,t.hideClass);return(n=s({},se,e,n,t)).showClass=o,n.hideClass=i,!1===t.animation&&(n.showClass={popup:"swal2-noanimation",backdrop:"swal2-noanimation"},n.hideClass={}),n}function Bn(a,c,s){return new Promise(function(t){function e(t){a.closePopup({isDismissed:!0,dismiss:t})}var n,o,i,r;ke.swalPromiseResolve.set(a,t),c.confirmButton.onclick=function(){return e=s,(t=a).disableButtons(),void(e.input?cn(t,e,"confirm"):ln(t,e,!0));var t,e},c.denyButton.onclick=function(){return e=s,(t=a).disableButtons(),void(e.returnInputValueOnDeny?cn(t,e,"deny"):un(t,e,!1));var t,e},c.cancelButton.onclick=function(){return t=e,a.disableButtons(),void t(Q.cancel);var t},c.closeButton.onclick=function(){return e(Q.close)},n=a,r=c,t=e,Tt.innerParams.get(n).toast?yn(n,r,t):(Cn(r),kn(r),An(n,r,t)),o=a,r=s,i=e,(t=ee).keydownTarget&&t.keydownHandlerAdded&&(t.keydownTarget.removeEventListener("keydown",t.keydownHandler,{capture:t.keydownListenerCapture}),t.keydownHandlerAdded=!1),r.toast||(t.keydownHandler=function(t){return mn(o,t,i)},t.keydownTarget=r.keydownListenerCapture?window:x(),t.keydownListenerCapture=r.keydownListenerCapture,t.keydownTarget.addEventListener("keydown",t.keydownHandler,{capture:t.keydownListenerCapture}),t.keydownHandlerAdded=!0),(s.toast&&(s.input||s.footer||s.showCloseButton)?vt:bt)(document.body,$["toast-column"]),r=a,"select"===(t=s).input||"radio"===t.input?en(r,t):-1!==["text","email","number","tel","textarea"].indexOf(t.input)&&(b(t.inputValue)||w(t.inputValue))&&nn(r,t),Ie(s),En(ee,s,e),Sn(c,s),setTimeout(function(){c.container.scrollTop=0})})}function Pn(t){var e={popup:x(),container:k(),content:E(),actions:I(),confirmButton:L(),denyButton:q(),cancelButton:j(),loader:D(),closeButton:R(),validationMessage:T(),progressSteps:O()};return Tt.domCache.set(t,e),e}var En=function(t,e,n){var o=V();at(o),e.timer&&(t.timeout=new qe(function(){n("timer"),delete t.timeout},e.timer),e.timerProgressBar&&(rt(o),setTimeout(function(){t.timeout&&t.timeout.running&&dt(e.timer)})))},Sn=function(t,e){if(!e.toast)return Z(e.allowEnterKey)?void(On(t,e)||Ve(0,-1,1)):Tn()},On=function(t,e){return e.focusDeny&&wt(t.denyButton)?(t.denyButton.focus(),!0):e.focusCancel&&wt(t.cancelButton)?(t.cancelButton.focus(),!0):!(!e.focusConfirm||!wt(t.confirmButton))&&(t.confirmButton.focus(),!0)},Tn=function(){document.activeElement&&"function"==typeof document.activeElement.blur&&document.activeElement.blur()};function Ln(t){"function"==typeof t.didDestroy?t.didDestroy():"function"==typeof t.onDestroy&&t.onDestroy()}function qn(t){delete t.params,delete ee.keydownHandler,delete ee.keydownTarget,jn(Tt),jn(ke)}var Dn,jn=function(t){for(var e in t)t[e]=new WeakMap},J=Object.freeze({hideLoading:fe,disableLoading:fe,getInput:function(t){var e=Tt.innerParams.get(t||this);return(t=Tt.domCache.get(t||this))?et(t.content,e.input):null},close:xe,closePopup:xe,closeModal:xe,closeToast:xe,enableButtons:function(){Te(this,["confirmButton","denyButton","cancelButton"],!1)},disableButtons:function(){Te(this,["confirmButton","denyButton","cancelButton"],!0)},enableInput:function(){return Le(this.getInput(),!1)},disableInput:function(){return Le(this.getInput(),!0)},showValidationMessage:function(t){var e=Tt.domCache.get(this),n=Tt.innerParams.get(this);_(e.validationMessage,t),e.validationMessage.className=$["validation-message"],n.customClass&&n.customClass.validationMessage&&vt(e.validationMessage,n.customClass.validationMessage),rt(e.validationMessage),(e=this.getInput())&&(e.setAttribute("aria-invalid",!0),e.setAttribute("aria-describedBy",$["validation-message"]),nt(e),vt(e,$.inputerror))},resetValidationMessage:function(){var t=Tt.domCache.get(this);t.validationMessage&&at(t.validationMessage),(t=this.getInput())&&(t.removeAttribute("aria-invalid"),t.removeAttribute("aria-describedBy"),bt(t,$.inputerror))},getProgressSteps:function(){return Tt.domCache.get(this).progressSteps},_main:function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};return ce(s({},e,t)),ee.currentInstance&&ee.currentInstance._destroy(),ee.currentInstance=this,je(t=xn(t,e)),Object.freeze(t),ee.timeout&&(ee.timeout.stop(),delete ee.timeout),clearTimeout(ee.restoreFocusTimeout),e=Pn(this),Ut(this,t),Tt.innerParams.set(this,t),Bn(this,e,t)},update:function(e){var t=x(),n=Tt.innerParams.get(this);if(!t||F(t,n.hideClass.popup))return W("You're trying to update the closed or closing popup, that won't work. Use the update() method in preConfirm parameter or show a new popup.");var o={};Object.keys(e).forEach(function(t){Mn.isUpdatableParameter(t)?o[t]=e[t]:W('Invalid parameter to update: "'.concat(t,'". Updatable params are listed here: https://github.com/sweetalert2/sweetalert2/blob/master/src/utils/params.js\n\nIf you think this parameter should be updatable, request it here: https://github.com/sweetalert2/sweetalert2/issues/new?template=02_feature_request.md'))}),n=s({},n,o),Ut(this,n),Tt.innerParams.set(this,n),Object.defineProperties(this,{params:{value:s({},this.params,e),writable:!1,enumerable:!0}})},_destroy:function(){var t=Tt.domCache.get(this),e=Tt.innerParams.get(this);e&&(t.popup&&ee.swalCloseEventFinishedCallback&&(ee.swalCloseEventFinishedCallback(),delete ee.swalCloseEventFinishedCallback),ee.deferDisposalTimer&&(clearTimeout(ee.deferDisposalTimer),delete ee.deferDisposalTimer),Ln(e),qn(this))}}),In=function(){function i(){if(a(this,i),"undefined"!=typeof window){"undefined"==typeof Promise&&K("This package requires a Promise library, please include a shim to enable it in this browser (See: https://github.com/sweetalert2/sweetalert2/wiki/Migration-from-SweetAlert-to-SweetAlert2#1-ie-support)"),Dn=this;for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];var o=Object.freeze(this.constructor.argsToParams(e));Object.defineProperties(this,{params:{value:o,writable:!1,enumerable:!0,configurable:!0}});o=this._main(this.params);Tt.promise.set(this,o)}}return c(i,[{key:"then",value:function(t){return Tt.promise.get(this).then(t)}},{key:"finally",value:function(t){return Tt.promise.get(this).finally(t)}}]),i}();s(In.prototype,J),s(In,pe),Object.keys(J).forEach(function(t){In[t]=function(){if(Dn)return Dn[t].apply(Dn,arguments)}}),In.DismissReason=Q,In.version="10.13.1";var Mn=In;return Mn.default=Mn}),void 0!==this&&this.Sweetalert2&&(this.swal=this.sweetAlert=this.Swal=this.SweetAlert=this.Sweetalert2); -"undefined"!=typeof document&&function(e,t){var n=e.createElement("style");if(e.getElementsByTagName("head")[0].appendChild(n),n.styleSheet)n.styleSheet.disabled||(n.styleSheet.cssText=t);else try{n.innerHTML=t}catch(e){n.innerText=t}}(document,".swal2-popup.swal2-toast{flex-direction:row;align-items:center;width:auto;padding:.625em;overflow-y:hidden;background:#fff;box-shadow:0 0 .625em #d9d9d9}.swal2-popup.swal2-toast .swal2-header{flex-direction:row;padding:0}.swal2-popup.swal2-toast .swal2-title{flex-grow:1;justify-content:flex-start;margin:0 .6em;font-size:1em}.swal2-popup.swal2-toast .swal2-footer{margin:.5em 0 0;padding:.5em 0 0;font-size:.8em}.swal2-popup.swal2-toast .swal2-close{position:static;width:.8em;height:.8em;line-height:.8}.swal2-popup.swal2-toast .swal2-content{justify-content:flex-start;padding:0;font-size:1em}.swal2-popup.swal2-toast .swal2-icon{width:2em;min-width:2em;height:2em;margin:0}.swal2-popup.swal2-toast .swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:1.8em;font-weight:700}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-popup.swal2-toast .swal2-icon .swal2-icon-content{font-size:.25em}}.swal2-popup.swal2-toast .swal2-icon.swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line]{top:.875em;width:1.375em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:.3125em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:.3125em}.swal2-popup.swal2-toast .swal2-actions{flex-basis:auto!important;width:auto;height:auto;margin:0 .3125em;padding:0}.swal2-popup.swal2-toast .swal2-styled{margin:.125em .3125em;padding:.3125em .625em;font-size:1em}.swal2-popup.swal2-toast .swal2-styled:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px rgba(100,150,200,.5)}.swal2-popup.swal2-toast .swal2-success{border-color:#a5dc86}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line]{position:absolute;width:1.6em;height:3em;transform:rotate(45deg);border-radius:50%}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.8em;left:-.5em;transform:rotate(-45deg);transform-origin:2em 2em;border-radius:4em 0 0 4em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.25em;left:.9375em;transform-origin:0 1.5em;border-radius:0 4em 4em 0}.swal2-popup.swal2-toast .swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-success .swal2-success-fix{top:0;left:.4375em;width:.4375em;height:2.6875em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line]{height:.3125em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=tip]{top:1.125em;left:.1875em;width:.75em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=long]{top:.9375em;right:.1875em;width:1.375em}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-tip{-webkit-animation:swal2-toast-animate-success-line-tip .75s;animation:swal2-toast-animate-success-line-tip .75s}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-long{-webkit-animation:swal2-toast-animate-success-line-long .75s;animation:swal2-toast-animate-success-line-long .75s}.swal2-popup.swal2-toast.swal2-show{-webkit-animation:swal2-toast-show .5s;animation:swal2-toast-show .5s}.swal2-popup.swal2-toast.swal2-hide{-webkit-animation:swal2-toast-hide .1s forwards;animation:swal2-toast-hide .1s forwards}.swal2-container{display:flex;position:fixed;z-index:1060;top:0;right:0;bottom:0;left:0;flex-direction:row;align-items:center;justify-content:center;padding:.625em;overflow-x:hidden;transition:background-color .1s;-webkit-overflow-scrolling:touch}.swal2-container.swal2-backdrop-show,.swal2-container.swal2-noanimation{background:rgba(0,0,0,.4)}.swal2-container.swal2-backdrop-hide{background:0 0!important}.swal2-container.swal2-top{align-items:flex-start}.swal2-container.swal2-top-left,.swal2-container.swal2-top-start{align-items:flex-start;justify-content:flex-start}.swal2-container.swal2-top-end,.swal2-container.swal2-top-right{align-items:flex-start;justify-content:flex-end}.swal2-container.swal2-center{align-items:center}.swal2-container.swal2-center-left,.swal2-container.swal2-center-start{align-items:center;justify-content:flex-start}.swal2-container.swal2-center-end,.swal2-container.swal2-center-right{align-items:center;justify-content:flex-end}.swal2-container.swal2-bottom{align-items:flex-end}.swal2-container.swal2-bottom-left,.swal2-container.swal2-bottom-start{align-items:flex-end;justify-content:flex-start}.swal2-container.swal2-bottom-end,.swal2-container.swal2-bottom-right{align-items:flex-end;justify-content:flex-end}.swal2-container.swal2-bottom-end>:first-child,.swal2-container.swal2-bottom-left>:first-child,.swal2-container.swal2-bottom-right>:first-child,.swal2-container.swal2-bottom-start>:first-child,.swal2-container.swal2-bottom>:first-child{margin-top:auto}.swal2-container.swal2-grow-fullscreen>.swal2-modal{display:flex!important;flex:1;align-self:stretch;justify-content:center}.swal2-container.swal2-grow-row>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-grow-column{flex:1;flex-direction:column}.swal2-container.swal2-grow-column.swal2-bottom,.swal2-container.swal2-grow-column.swal2-center,.swal2-container.swal2-grow-column.swal2-top{align-items:center}.swal2-container.swal2-grow-column.swal2-bottom-left,.swal2-container.swal2-grow-column.swal2-bottom-start,.swal2-container.swal2-grow-column.swal2-center-left,.swal2-container.swal2-grow-column.swal2-center-start,.swal2-container.swal2-grow-column.swal2-top-left,.swal2-container.swal2-grow-column.swal2-top-start{align-items:flex-start}.swal2-container.swal2-grow-column.swal2-bottom-end,.swal2-container.swal2-grow-column.swal2-bottom-right,.swal2-container.swal2-grow-column.swal2-center-end,.swal2-container.swal2-grow-column.swal2-center-right,.swal2-container.swal2-grow-column.swal2-top-end,.swal2-container.swal2-grow-column.swal2-top-right{align-items:flex-end}.swal2-container.swal2-grow-column>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-no-transition{transition:none!important}.swal2-container:not(.swal2-top):not(.swal2-top-start):not(.swal2-top-end):not(.swal2-top-left):not(.swal2-top-right):not(.swal2-center-start):not(.swal2-center-end):not(.swal2-center-left):not(.swal2-center-right):not(.swal2-bottom):not(.swal2-bottom-start):not(.swal2-bottom-end):not(.swal2-bottom-left):not(.swal2-bottom-right):not(.swal2-grow-fullscreen)>.swal2-modal{margin:auto}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-container .swal2-modal{margin:0!important}}.swal2-popup{display:none;position:relative;box-sizing:border-box;flex-direction:column;justify-content:center;width:32em;max-width:100%;padding:1.25em;border:none;border-radius:5px;background:#fff;font-family:inherit;font-size:1rem}.swal2-popup:focus{outline:0}.swal2-popup.swal2-loading{overflow-y:hidden}.swal2-header{display:flex;flex-direction:column;align-items:center;padding:0 1.8em}.swal2-title{position:relative;max-width:100%;margin:0 0 .4em;padding:0;color:#595959;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word}.swal2-actions{display:flex;z-index:1;box-sizing:border-box;flex-wrap:wrap;align-items:center;justify-content:center;width:100%;margin:1.25em auto 0;padding:0 1.6em}.swal2-actions:not(.swal2-loading) .swal2-styled[disabled]{opacity:.4}.swal2-actions:not(.swal2-loading) .swal2-styled:hover{background-image:linear-gradient(rgba(0,0,0,.1),rgba(0,0,0,.1))}.swal2-actions:not(.swal2-loading) .swal2-styled:active{background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.2))}.swal2-loader{display:none;align-items:center;justify-content:center;width:2.2em;height:2.2em;margin:0 1.875em;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border-width:.25em;border-style:solid;border-radius:100%;border-color:#2778c4 transparent #2778c4 transparent}.swal2-styled{margin:.3125em;padding:.625em 1.1em;box-shadow:none;font-weight:500}.swal2-styled:not([disabled]){cursor:pointer}.swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:#2778c4;color:#fff;font-size:1.0625em}.swal2-styled.swal2-deny{border:0;border-radius:.25em;background:initial;background-color:#d14529;color:#fff;font-size:1.0625em}.swal2-styled.swal2-cancel{border:0;border-radius:.25em;background:initial;background-color:#757575;color:#fff;font-size:1.0625em}.swal2-styled:focus{outline:0;box-shadow:0 0 0 3px rgba(100,150,200,.5)}.swal2-styled::-moz-focus-inner{border:0}.swal2-footer{justify-content:center;margin:1.25em 0 0;padding:1em 0 0;border-top:1px solid #eee;color:#545454;font-size:1em}.swal2-timer-progress-bar-container{position:absolute;right:0;bottom:0;left:0;height:.25em;overflow:hidden;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.swal2-timer-progress-bar{width:100%;height:.25em;background:rgba(0,0,0,.2)}.swal2-image{max-width:100%;margin:1.25em auto}.swal2-close{position:absolute;z-index:2;top:0;right:0;align-items:center;justify-content:center;width:1.2em;height:1.2em;padding:0;overflow:hidden;transition:color .1s ease-out;border:none;border-radius:5px;background:0 0;color:#ccc;font-family:serif;font-size:2.5em;line-height:1.2;cursor:pointer}.swal2-close:hover{transform:none;background:0 0;color:#f27474}.swal2-close:focus{outline:0;box-shadow:inset 0 0 0 3px rgba(100,150,200,.5)}.swal2-close::-moz-focus-inner{border:0}.swal2-content{z-index:1;justify-content:center;margin:0;padding:0 1.6em;color:#545454;font-size:1.125em;font-weight:400;line-height:normal;text-align:center;word-wrap:break-word}.swal2-checkbox,.swal2-file,.swal2-input,.swal2-radio,.swal2-select,.swal2-textarea{margin:1em auto}.swal2-file,.swal2-input,.swal2-textarea{box-sizing:border-box;width:100%;transition:border-color .3s,box-shadow .3s;border:1px solid #d9d9d9;border-radius:.1875em;background:inherit;box-shadow:inset 0 1px 1px rgba(0,0,0,.06);color:inherit;font-size:1.125em}.swal2-file.swal2-inputerror,.swal2-input.swal2-inputerror,.swal2-textarea.swal2-inputerror{border-color:#f27474!important;box-shadow:0 0 2px #f27474!important}.swal2-file:focus,.swal2-input:focus,.swal2-textarea:focus{border:1px solid #b4dbed;outline:0;box-shadow:0 0 0 3px rgba(100,150,200,.5)}.swal2-file::-moz-placeholder,.swal2-input::-moz-placeholder,.swal2-textarea::-moz-placeholder{color:#ccc}.swal2-file:-ms-input-placeholder,.swal2-input:-ms-input-placeholder,.swal2-textarea:-ms-input-placeholder{color:#ccc}.swal2-file::placeholder,.swal2-input::placeholder,.swal2-textarea::placeholder{color:#ccc}.swal2-range{margin:1em auto;background:#fff}.swal2-range input{width:80%}.swal2-range output{width:20%;color:inherit;font-weight:600;text-align:center}.swal2-range input,.swal2-range output{height:2.625em;padding:0;font-size:1.125em;line-height:2.625em}.swal2-input{height:2.625em;padding:0 .75em}.swal2-input[type=number]{max-width:10em}.swal2-file{background:inherit;font-size:1.125em}.swal2-textarea{height:6.75em;padding:.75em}.swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:inherit;color:inherit;font-size:1.125em}.swal2-checkbox,.swal2-radio{align-items:center;justify-content:center;background:#fff;color:inherit}.swal2-checkbox label,.swal2-radio label{margin:0 .6em;font-size:1.125em}.swal2-checkbox input,.swal2-radio input{margin:0 .4em}.swal2-input-label{display:flex;justify-content:center;margin:1em auto}.swal2-validation-message{display:none;align-items:center;justify-content:center;margin:0 -2.7em;padding:.625em;overflow:hidden;background:#f0f0f0;color:#666;font-size:1em;font-weight:300}.swal2-validation-message::before{content:\"!\";display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center}.swal2-icon{position:relative;box-sizing:content-box;justify-content:center;width:5em;height:5em;margin:1.25em auto 1.875em;border:.25em solid transparent;border-radius:50%;font-family:inherit;line-height:5em;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:3.75em}.swal2-icon.swal2-error{border-color:#f27474;color:#f27474}.swal2-icon.swal2-error .swal2-x-mark{position:relative;flex-grow:1}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;transform:rotate(45deg)}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;transform:rotate(-45deg)}.swal2-icon.swal2-error.swal2-icon-show{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-icon.swal2-error.swal2-icon-show .swal2-x-mark{-webkit-animation:swal2-animate-error-x-mark .5s;animation:swal2-animate-error-x-mark .5s}.swal2-icon.swal2-warning{border-color:#facea8;color:#f8bb86}.swal2-icon.swal2-info{border-color:#9de0f6;color:#3fc3ee}.swal2-icon.swal2-question{border-color:#c9dae1;color:#87adbd}.swal2-icon.swal2-success{border-color:#a5dc86;color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;transform:rotate(45deg);border-radius:50%}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.4375em;left:-2.0635em;transform:rotate(-45deg);transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.6875em;left:1.875em;transform:rotate(-45deg);transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}.swal2-icon.swal2-success .swal2-success-ring{position:absolute;z-index:2;top:-.25em;left:-.25em;box-sizing:content-box;width:100%;height:100%;border:.25em solid rgba(165,220,134,.3);border-radius:50%}.swal2-icon.swal2-success .swal2-success-fix{position:absolute;z-index:1;top:.5em;left:1.625em;width:.4375em;height:5.625em;transform:rotate(-45deg)}.swal2-icon.swal2-success [class^=swal2-success-line]{display:block;position:absolute;z-index:2;height:.3125em;border-radius:.125em;background-color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.8125em;width:1.5625em;transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;transform:rotate(-45deg)}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-tip{-webkit-animation:swal2-animate-success-line-tip .75s;animation:swal2-animate-success-line-tip .75s}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-long{-webkit-animation:swal2-animate-success-line-long .75s;animation:swal2-animate-success-line-long .75s}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-circular-line-right{-webkit-animation:swal2-rotate-success-circular-line 4.25s ease-in;animation:swal2-rotate-success-circular-line 4.25s ease-in}.swal2-progress-steps{flex-wrap:wrap;align-items:center;max-width:100%;margin:0 0 1.25em;padding:0;background:inherit;font-weight:600}.swal2-progress-steps li{display:inline-block;position:relative}.swal2-progress-steps .swal2-progress-step{z-index:20;flex-shrink:0;width:2em;height:2em;border-radius:2em;background:#2778c4;color:#fff;line-height:2em;text-align:center}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:#2778c4}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step{background:#add8e6;color:#fff}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line{background:#add8e6}.swal2-progress-steps .swal2-progress-step-line{z-index:10;flex-shrink:0;width:2.5em;height:.4em;margin:0 -1px;background:#2778c4}[class^=swal2]{-webkit-tap-highlight-color:transparent}.swal2-show{-webkit-animation:swal2-show .3s;animation:swal2-show .3s}.swal2-hide{-webkit-animation:swal2-hide .15s forwards;animation:swal2-hide .15s forwards}.swal2-noanimation{transition:none}.swal2-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}.swal2-rtl .swal2-close{right:auto;left:0}.swal2-rtl .swal2-timer-progress-bar{right:0;left:auto}@supports (-ms-accelerator:true){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@-webkit-keyframes swal2-toast-show{0%{transform:translateY(-.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0)}}@keyframes swal2-toast-show{0%{transform:translateY(-.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0)}}@-webkit-keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@-webkit-keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@-webkit-keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@-webkit-keyframes swal2-show{0%{transform:scale(.7)}45%{transform:scale(1.05)}80%{transform:scale(.95)}100%{transform:scale(1)}}@keyframes swal2-show{0%{transform:scale(.7)}45%{transform:scale(1.05)}80%{transform:scale(.95)}100%{transform:scale(1)}}@-webkit-keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(.5);opacity:0}}@keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(.5);opacity:0}}@-webkit-keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@-webkit-keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@-webkit-keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@-webkit-keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(.4);opacity:0}50%{margin-top:1.625em;transform:scale(.4);opacity:0}80%{margin-top:-.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(.4);opacity:0}50%{margin-top:1.625em;transform:scale(.4);opacity:0}80%{margin-top:-.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@-webkit-keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);opacity:1}}@keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);opacity:1}}@-webkit-keyframes swal2-rotate-loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes swal2-rotate-loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow:hidden}body.swal2-height-auto{height:auto!important}body.swal2-no-backdrop .swal2-container{top:auto;right:auto;bottom:auto;left:auto;max-width:calc(100% - .625em * 2);background-color:transparent!important}body.swal2-no-backdrop .swal2-container>.swal2-modal{box-shadow:0 0 10px rgba(0,0,0,.4)}body.swal2-no-backdrop .swal2-container.swal2-top{top:0;left:50%;transform:translateX(-50%)}body.swal2-no-backdrop .swal2-container.swal2-top-left,body.swal2-no-backdrop .swal2-container.swal2-top-start{top:0;left:0}body.swal2-no-backdrop .swal2-container.swal2-top-end,body.swal2-no-backdrop .swal2-container.swal2-top-right{top:0;right:0}body.swal2-no-backdrop .swal2-container.swal2-center{top:50%;left:50%;transform:translate(-50%,-50%)}body.swal2-no-backdrop .swal2-container.swal2-center-left,body.swal2-no-backdrop .swal2-container.swal2-center-start{top:50%;left:0;transform:translateY(-50%)}body.swal2-no-backdrop .swal2-container.swal2-center-end,body.swal2-no-backdrop .swal2-container.swal2-center-right{top:50%;right:0;transform:translateY(-50%)}body.swal2-no-backdrop .swal2-container.swal2-bottom{bottom:0;left:50%;transform:translateX(-50%)}body.swal2-no-backdrop .swal2-container.swal2-bottom-left,body.swal2-no-backdrop .swal2-container.swal2-bottom-start{bottom:0;left:0}body.swal2-no-backdrop .swal2-container.swal2-bottom-end,body.swal2-no-backdrop .swal2-container.swal2-bottom-right{right:0;bottom:0}@media print{body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow-y:scroll!important}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container{position:static!important}}body.swal2-toast-shown .swal2-container{background-color:transparent}body.swal2-toast-shown .swal2-container.swal2-top{top:0;right:auto;bottom:auto;left:50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-top-end,body.swal2-toast-shown .swal2-container.swal2-top-right{top:0;right:0;bottom:auto;left:auto}body.swal2-toast-shown .swal2-container.swal2-top-left,body.swal2-toast-shown .swal2-container.swal2-top-start{top:0;right:auto;bottom:auto;left:0}body.swal2-toast-shown .swal2-container.swal2-center-left,body.swal2-toast-shown .swal2-container.swal2-center-start{top:50%;right:auto;bottom:auto;left:0;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-center{top:50%;right:auto;bottom:auto;left:50%;transform:translate(-50%,-50%)}body.swal2-toast-shown .swal2-container.swal2-center-end,body.swal2-toast-shown .swal2-container.swal2-center-right{top:50%;right:0;bottom:auto;left:auto;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-left,body.swal2-toast-shown .swal2-container.swal2-bottom-start{top:auto;right:auto;bottom:0;left:0}body.swal2-toast-shown .swal2-container.swal2-bottom{top:auto;right:auto;bottom:0;left:50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-end,body.swal2-toast-shown .swal2-container.swal2-bottom-right{top:auto;right:0;bottom:0;left:auto}body.swal2-toast-column .swal2-toast{flex-direction:column;align-items:stretch}body.swal2-toast-column .swal2-toast .swal2-actions{flex:1;align-self:stretch;height:2.2em;margin-top:.3125em}body.swal2-toast-column .swal2-toast .swal2-loading{justify-content:center}body.swal2-toast-column .swal2-toast .swal2-input{height:2em;margin:.3125em auto;font-size:1em}body.swal2-toast-column .swal2-toast .swal2-validation-message{font-size:1em}"); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sweetalert2=e()}(this,function(){"use strict";function r(t){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function a(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function c(t,e,n){return e&&o(t.prototype,e),n&&o(t,n),t}function s(){return(s=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n,o=arguments[e];for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&(t[n]=o[n])}return t}).apply(this,arguments)}function u(t){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function l(t,e){return(l=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function d(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}function i(t,e,n){return(i=d()?Reflect.construct:function(t,e,n){var o=[null];o.push.apply(o,e);o=new(Function.bind.apply(t,o));return n&&l(o,n.prototype),o}).apply(null,arguments)}function p(t,e){return!e||"object"!=typeof e&&"function"!=typeof e?function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t):e}function f(t,e,n){return(f="undefined"!=typeof Reflect&&Reflect.get?Reflect.get:function(t,e,n){t=function(t,e){for(;!Object.prototype.hasOwnProperty.call(t,e)&&null!==(t=u(t)););return t}(t,e);if(t){e=Object.getOwnPropertyDescriptor(t,e);return e.get?e.get.call(n):e.value}})(t,e,n||t)}function m(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(e){return Object.keys(e).map(function(t){return e[t]})}function g(t){return Array.prototype.slice.call(t)}function v(t,e){e='"'.concat(t,'" is deprecated and will be removed in the next major release. Please use "').concat(e,'" instead.'),-1===Y.indexOf(e)&&(Y.push(e),W(e))}function b(t){return t&&"function"==typeof t.toPromise}function y(t){return b(t)?t.toPromise():Promise.resolve(t)}function w(t){return t&&Promise.resolve(t)===t}function C(t){return t instanceof Element||"object"===r(t=t)&&t.jquery}function k(){return document.body.querySelector(".".concat($.container))}function A(t){var e=k();return e?e.querySelector(t):null}function t(t){return A(".".concat(t))}function x(){return t($.popup)}function n(){var t=x();return g(t.querySelectorAll(".".concat($.icon)))}function B(){var t=n().filter(function(t){return"none"!==t.style.display});return t.length?t[0]:null}function P(){return t($.title)}function E(){return t($.content)}function O(){return t($.image)}function S(){return t($["progress-steps"])}function T(){return t($["validation-message"])}function L(){return A(".".concat($.actions," .").concat($.confirm))}function q(){return A(".".concat($.actions," .").concat($.deny))}function D(){return A(".".concat($.loader))}function j(){return A(".".concat($.actions," .").concat($.cancel))}function I(){return t($.actions)}function M(){return t($.header)}function H(){return t($.footer)}function V(){return t($["timer-progress-bar"])}function R(){return t($.close)}function N(){var t=g(x().querySelectorAll('[tabindex]:not([tabindex="-1"]):not([tabindex="0"])')).sort(function(t,e){return t=parseInt(t.getAttribute("tabindex")),(e=parseInt(e.getAttribute("tabindex")))<t?1:t<e?-1:0}),e=g(x().querySelectorAll('\n a[href],\n area[href],\n input:not([disabled]),\n select:not([disabled]),\n textarea:not([disabled]),\n button:not([disabled]),\n iframe,\n object,\n embed,\n [tabindex="0"],\n [contenteditable],\n audio[controls],\n video[controls],\n summary\n')).filter(function(t){return"-1"!==t.getAttribute("tabindex")});return function(t){for(var e=[],n=0;n<t.length;n++)-1===e.indexOf(t[n])&&e.push(t[n]);return e}(t.concat(e)).filter(function(t){return wt(t)})}function U(){return!G()&&!document.body.classList.contains($["no-backdrop"])}function _(e,t){e.textContent="",t&&(t=(new DOMParser).parseFromString(t,"text/html"),g(t.querySelector("head").childNodes).forEach(function(t){e.appendChild(t)}),g(t.querySelector("body").childNodes).forEach(function(t){e.appendChild(t)}))}function F(t,e){if(e){for(var n=e.split(/\s+/),o=0;o<n.length;o++)if(!t.classList.contains(n[o]))return;return 1}}function z(t,e,n){var o,i;if(i=e,g((o=t).classList).forEach(function(t){-1===h($).indexOf(t)&&-1===h(X).indexOf(t)&&-1===h(i.showClass).indexOf(t)&&o.classList.remove(t)}),e.customClass&&e.customClass[n]){if("string"!=typeof e.customClass[n]&&!e.customClass[n].forEach)return W("Invalid type of customClass.".concat(n,'! Expected string or iterable object, got "').concat(r(e.customClass[n]),'"'));vt(t,e.customClass[n])}}var e="SweetAlert2:",W=function(t){console.warn("".concat(e," ").concat("object"===r(t)?t.join(" "):t))},K=function(t){console.error("".concat(e," ").concat(t))},Y=[],Z=function(t){return"function"==typeof t?t():t},Q=Object.freeze({cancel:"cancel",backdrop:"backdrop",close:"close",esc:"esc",timer:"timer"}),J=function(t){var e,n={};for(e in t)n[t[e]]="swal2-"+t[e];return n},$=J(["container","shown","height-auto","iosfix","popup","modal","no-backdrop","no-transition","toast","toast-shown","toast-column","show","hide","close","title","header","content","html-container","actions","confirm","deny","cancel","footer","icon","icon-content","image","input","file","range","select","radio","checkbox","label","textarea","inputerror","input-label","validation-message","progress-steps","active-progress-step","progress-step","progress-step-line","loader","loading","styled","top","top-start","top-end","top-left","top-right","center","center-start","center-end","center-left","center-right","bottom","bottom-start","bottom-end","bottom-left","bottom-right","grow-row","grow-column","grow-fullscreen","rtl","timer-progress-bar","timer-progress-bar-container","scrollbar-measure","icon-success","icon-warning","icon-info","icon-question","icon-error"]),X=J(["success","warning","info","question","error"]),G=function(){return document.body.classList.contains($["toast-shown"])},tt={previousBodyPadding:null};function et(t,e){if(!e)return null;switch(e){case"select":case"textarea":case"file":return yt(t,$[e]);case"checkbox":return t.querySelector(".".concat($.checkbox," input"));case"radio":return t.querySelector(".".concat($.radio," input:checked"))||t.querySelector(".".concat($.radio," input:first-child"));case"range":return t.querySelector(".".concat($.range," input"));default:return yt(t,$.input)}}function nt(t){var e;t.focus(),"file"!==t.type&&(e=t.value,t.value="",t.value=e)}function ot(t,e,n){t&&e&&("string"==typeof e&&(e=e.split(/\s+/).filter(Boolean)),e.forEach(function(e){t.forEach?t.forEach(function(t){n?t.classList.add(e):t.classList.remove(e)}):n?t.classList.add(e):t.classList.remove(e)}))}function it(t,e,n){n==="".concat(parseInt(n))&&(n=parseInt(n)),n||0===parseInt(n)?t.style[e]="number"==typeof n?"".concat(n,"px"):n:t.style.removeProperty(e)}function rt(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:"flex";t.style.display=e}function at(t){t.style.display="none"}function ct(t,e,n,o){(e=t.querySelector(e))&&(e.style[n]=o)}function st(t,e,n){e?rt(t,n):at(t)}function ut(t){return!!(t.scrollHeight>t.clientHeight)}function lt(t){var e=window.getComputedStyle(t),t=parseFloat(e.getPropertyValue("animation-duration")||"0"),e=parseFloat(e.getPropertyValue("transition-duration")||"0");return 0<t||0<e}function dt(t){var e=1<arguments.length&&void 0!==arguments[1]&&arguments[1],n=V();wt(n)&&(e&&(n.style.transition="none",n.style.width="100%"),setTimeout(function(){n.style.transition="width ".concat(t/1e3,"s linear"),n.style.width="0%"},10))}function pt(){return"undefined"==typeof window||"undefined"==typeof document}function ft(t){Mn.isVisible()&>!==t.target.value&&Mn.resetValidationMessage(),gt=t.target.value}function mt(t,e){t instanceof HTMLElement?e.appendChild(t):"object"===r(t)?At(t,e):t&&_(e,t)}function ht(t,e){var n,o,i,r,a=I(),c=D(),s=L(),u=q(),l=j();e.showConfirmButton||e.showDenyButton||e.showCancelButton||at(a),z(a,e,"actions"),Pt(s,"confirm",e),Pt(u,"deny",e),Pt(l,"cancel",e),n=s,o=u,i=l,(r=e).buttonsStyling?(vt([n,o,i],$.styled),r.confirmButtonColor&&(n.style.backgroundColor=r.confirmButtonColor),r.denyButtonColor&&(o.style.backgroundColor=r.denyButtonColor),r.cancelButtonColor&&(i.style.backgroundColor=r.cancelButtonColor)):bt([n,o,i],$.styled),e.reverseButtons&&(a.insertBefore(l,c),a.insertBefore(u,c),a.insertBefore(s,c)),_(c,e.loaderHtml),z(c,e,"loader")}var gt,vt=function(t,e){ot(t,e,!0)},bt=function(t,e){ot(t,e,!1)},yt=function(t,e){for(var n=0;n<t.childNodes.length;n++)if(F(t.childNodes[n],e))return t.childNodes[n]},wt=function(t){return!(!t||!(t.offsetWidth||t.offsetHeight||t.getClientRects().length))},Ct='\n <div aria-labelledby="'.concat($.title,'" aria-describedby="').concat($.content,'" class="').concat($.popup,'" tabindex="-1">\n <div class="').concat($.header,'">\n <ul class="').concat($["progress-steps"],'"></ul>\n <div class="').concat($.icon," ").concat(X.error,'"></div>\n <div class="').concat($.icon," ").concat(X.question,'"></div>\n <div class="').concat($.icon," ").concat(X.warning,'"></div>\n <div class="').concat($.icon," ").concat(X.info,'"></div>\n <div class="').concat($.icon," ").concat(X.success,'"></div>\n <img class="').concat($.image,'" />\n <h2 class="').concat($.title,'" id="').concat($.title,'"></h2>\n <button type="button" class="').concat($.close,'"></button>\n </div>\n <div class="').concat($.content,'">\n <div id="').concat($.content,'" class="').concat($["html-container"],'"></div>\n <input class="').concat($.input,'" />\n <input type="file" class="').concat($.file,'" />\n <div class="').concat($.range,'">\n <input type="range" />\n <output></output>\n </div>\n <select class="').concat($.select,'"></select>\n <div class="').concat($.radio,'"></div>\n <label for="').concat($.checkbox,'" class="').concat($.checkbox,'">\n <input type="checkbox" />\n <span class="').concat($.label,'"></span>\n </label>\n <textarea class="').concat($.textarea,'"></textarea>\n <div class="').concat($["validation-message"],'" id="').concat($["validation-message"],'"></div>\n </div>\n <div class="').concat($.actions,'">\n <div class="').concat($.loader,'"></div>\n <button type="button" class="').concat($.confirm,'"></button>\n <button type="button" class="').concat($.deny,'"></button>\n <button type="button" class="').concat($.cancel,'"></button>\n </div>\n <div class="').concat($.footer,'"></div>\n <div class="').concat($["timer-progress-bar-container"],'">\n <div class="').concat($["timer-progress-bar"],'"></div>\n </div>\n </div>\n').replace(/(^|\n)\s*/g,""),kt=function(t){var e,n,o,i,r,a=!!(i=k())&&(i.parentNode.removeChild(i),bt([document.documentElement,document.body],[$["no-backdrop"],$["toast-shown"],$["has-column"]]),!0);pt()?K("SweetAlert2 requires document to initialize"):((r=document.createElement("div")).className=$.container,a&&vt(r,$["no-transition"]),_(r,Ct),(i="string"==typeof(e=t.target)?document.querySelector(e):e).appendChild(r),a=t,(e=x()).setAttribute("role",a.toast?"alert":"dialog"),e.setAttribute("aria-live",a.toast?"polite":"assertive"),a.toast||e.setAttribute("aria-modal","true"),r=i,"rtl"===window.getComputedStyle(r).direction&&vt(k(),$.rtl),t=E(),a=yt(t,$.input),e=yt(t,$.file),n=t.querySelector(".".concat($.range," input")),o=t.querySelector(".".concat($.range," output")),i=yt(t,$.select),r=t.querySelector(".".concat($.checkbox," input")),t=yt(t,$.textarea),a.oninput=ft,e.onchange=ft,i.onchange=ft,r.onchange=ft,t.oninput=ft,n.oninput=function(t){ft(t),o.value=n.value},n.onchange=function(t){ft(t),n.nextSibling.value=n.value})},At=function(t,e){t.jquery?xt(e,t):_(e,t.toString())},xt=function(t,e){if(t.textContent="",0 in e)for(var n=0;n in e;n++)t.appendChild(e[n].cloneNode(!0));else t.appendChild(e.cloneNode(!0))},Bt=function(){if(pt())return!1;var t,e=document.createElement("div"),n={WebkitAnimation:"webkitAnimationEnd",OAnimation:"oAnimationEnd oanimationend",animation:"animationend"};for(t in n)if(Object.prototype.hasOwnProperty.call(n,t)&&void 0!==e.style[t])return n[t];return!1}();function Pt(t,e,n){st(t,n["show".concat(m(e),"Button")],"inline-block"),_(t,n["".concat(e,"ButtonText")]),t.setAttribute("aria-label",n["".concat(e,"ButtonAriaLabel")]),t.className=$[e],z(t,n,"".concat(e,"Button")),vt(t,n["".concat(e,"ButtonClass")])}function Et(t,e){var n,o,i=k();i&&(o=i,"string"==typeof(n=e.backdrop)?o.style.background=n:n||vt([document.documentElement,document.body],$["no-backdrop"]),!e.backdrop&&e.allowOutsideClick&&W('"allowOutsideClick" parameter requires `backdrop` parameter to be set to `true`'),o=i,(n=e.position)in $?vt(o,$[n]):(W('The "position" parameter is not valid, defaulting to "center"'),vt(o,$.center)),n=i,!(o=e.grow)||"string"!=typeof o||(o="grow-".concat(o))in $&&vt(n,$[o]),z(i,e,"container"),(e=document.body.getAttribute("data-swal2-queue-step"))&&(i.setAttribute("data-queue-step",e),document.body.removeAttribute("data-swal2-queue-step")))}function Ot(t,e){t.placeholder&&!e.inputPlaceholder||(t.placeholder=e.inputPlaceholder)}function St(t,e,n){var o,i;n.inputLabel&&(t.id=$.input,o=document.createElement("label"),i=$["input-label"],o.setAttribute("for",t.id),o.className=i,vt(o,n.customClass.inputLabel),o.innerText=n.inputLabel,e.insertAdjacentElement("beforebegin",o))}var Tt={promise:new WeakMap,innerParams:new WeakMap,domCache:new WeakMap},Lt=["input","file","range","select","radio","checkbox","textarea"],qt=function(t){if(!Mt[t.input])return K('Unexpected type of input! Expected "text", "email", "password", "number", "tel", "select", "radio", "checkbox", "textarea", "file" or "url", got "'.concat(t.input,'"'));var e=It(t.input),n=Mt[t.input](e,t);rt(n),setTimeout(function(){nt(n)})},Dt=function(t,e){var n=et(E(),t);if(n)for(var o in!function(t){for(var e=0;e<t.attributes.length;e++){var n=t.attributes[e].name;-1===["type","value","style"].indexOf(n)&&t.removeAttribute(n)}}(n),e)"range"===t&&"placeholder"===o||n.setAttribute(o,e[o])},jt=function(t){var e=It(t.input);t.customClass&&vt(e,t.customClass.input)},It=function(t){t=$[t]||$.input;return yt(E(),t)},Mt={};Mt.text=Mt.email=Mt.password=Mt.number=Mt.tel=Mt.url=function(t,e){return"string"==typeof e.inputValue||"number"==typeof e.inputValue?t.value=e.inputValue:w(e.inputValue)||W('Unexpected type of inputValue! Expected "string", "number" or "Promise", got "'.concat(r(e.inputValue),'"')),St(t,t,e),Ot(t,e),t.type=e.input,t},Mt.file=function(t,e){return St(t,t,e),Ot(t,e),t},Mt.range=function(t,e){var n=t.querySelector("input"),o=t.querySelector("output");return n.value=e.inputValue,n.type=e.input,o.value=e.inputValue,St(n,t,e),t},Mt.select=function(t,e){var n;return t.textContent="",e.inputPlaceholder&&(n=document.createElement("option"),_(n,e.inputPlaceholder),n.value="",n.disabled=!0,n.selected=!0,t.appendChild(n)),St(t,t,e),t},Mt.radio=function(t){return t.textContent="",t},Mt.checkbox=function(t,e){var n=et(E(),"checkbox");n.value=1,n.id=$.checkbox,n.checked=Boolean(e.inputValue);n=t.querySelector("span");return _(n,e.inputPlaceholder),t},Mt.textarea=function(e,t){e.value=t.inputValue,Ot(e,t),St(e,e,t);function n(t){return parseInt(window.getComputedStyle(t).paddingLeft)+parseInt(window.getComputedStyle(t).paddingRight)}var o;return"MutationObserver"in window&&(o=parseInt(window.getComputedStyle(x()).width),new MutationObserver(function(){var t=e.offsetWidth+n(x())+n(E());x().style.width=o<t?"".concat(t,"px"):null}).observe(e,{attributes:!0,attributeFilter:["style"]})),e};function Ht(t,e){var o,i,r,n=E().querySelector("#".concat($.content));e.html?(mt(e.html,n),rt(n,"block")):e.text?(n.textContent=e.text,rt(n,"block")):at(n),t=t,o=e,i=E(),t=Tt.innerParams.get(t),r=!t||o.input!==t.input,Lt.forEach(function(t){var e=$[t],n=yt(i,e);Dt(t,o.inputAttributes),n.className=e,r&&at(n)}),o.input&&(r&&qt(o),jt(o)),z(E(),e,"content")}function Vt(){return k()&&k().getAttribute("data-queue-step")}function Rt(t,o){var i=S();if(!o.progressSteps||0===o.progressSteps.length)return at(i),0;rt(i),i.textContent="";var r=parseInt(void 0===o.currentProgressStep?Vt():o.currentProgressStep);r>=o.progressSteps.length&&W("Invalid currentProgressStep parameter, it should be less than progressSteps.length (currentProgressStep like JS arrays starts from 0)"),o.progressSteps.forEach(function(t,e){var n,t=(n=t,t=document.createElement("li"),vt(t,$["progress-step"]),_(t,n),t);i.appendChild(t),e===r&&vt(t,$["active-progress-step"]),e!==o.progressSteps.length-1&&(t=o,e=document.createElement("li"),vt(e,$["progress-step-line"]),t.progressStepsDistance&&(e.style.width=t.progressStepsDistance),e=e,i.appendChild(e))})}function Nt(t,e){var n=M();z(n,e,"header"),Rt(0,e),n=t,t=e,(n=Tt.innerParams.get(n))&&t.icon===n.icon&&B()?zt(B(),t):(Ft(),t.icon&&(-1!==Object.keys(X).indexOf(t.icon)?(n=A(".".concat($.icon,".").concat(X[t.icon])),rt(n),Kt(n,t),zt(n,t),vt(n,t.showClass.icon)):K('Unknown icon! Expected "success", "error", "warning", "info" or "question", got "'.concat(t.icon,'"')))),function(t){var e=O();if(!t.imageUrl)return at(e);rt(e,""),e.setAttribute("src",t.imageUrl),e.setAttribute("alt",t.imageAlt),it(e,"width",t.imageWidth),it(e,"height",t.imageHeight),e.className=$.image,z(e,t,"image")}(e),n=e,t=P(),st(t,n.title||n.titleText),n.title&&mt(n.title,t),n.titleText&&(t.innerText=n.titleText),z(t,n,"title"),n=e,e=R(),_(e,n.closeButtonHtml),z(e,n,"closeButton"),st(e,n.showCloseButton),e.setAttribute("aria-label",n.closeButtonAriaLabel)}function Ut(t,e){var n,o;o=e,n=x(),it(n,"width",o.width),it(n,"padding",o.padding),o.background&&(n.style.background=o.background),Jt(n,o),Et(0,e),Nt(t,e),Ht(t,e),ht(0,e),o=e,t=H(),st(t,o.footer),o.footer&&mt(o.footer,t),z(t,o,"footer"),"function"==typeof e.didRender?e.didRender(x()):"function"==typeof e.onRender&&e.onRender(x())}function _t(){return L()&&L().click()}var Ft=function(){for(var t=n(),e=0;e<t.length;e++)at(t[e])},zt=function(t,e){Yt(t,e),Wt(),z(t,e,"icon")},Wt=function(){for(var t=x(),e=window.getComputedStyle(t).getPropertyValue("background-color"),n=t.querySelectorAll("[class^=swal2-success-circular-line], .swal2-success-fix"),o=0;o<n.length;o++)n[o].style.backgroundColor=e},Kt=function(t,e){t.textContent="",e.iconHtml?_(t,Zt(e.iconHtml)):"success"===e.icon?_(t,'\n <div class="swal2-success-circular-line-left"></div>\n <span class="swal2-success-line-tip"></span> <span class="swal2-success-line-long"></span>\n <div class="swal2-success-ring"></div> <div class="swal2-success-fix"></div>\n <div class="swal2-success-circular-line-right"></div>\n '):"error"===e.icon?_(t,'\n <span class="swal2-x-mark">\n <span class="swal2-x-mark-line-left"></span>\n <span class="swal2-x-mark-line-right"></span>\n </span>\n '):_(t,Zt({question:"?",warning:"!",info:"i"}[e.icon]))},Yt=function(t,e){if(e.iconColor){t.style.color=e.iconColor,t.style.borderColor=e.iconColor;for(var n=0,o=[".swal2-success-line-tip",".swal2-success-line-long",".swal2-x-mark-line-left",".swal2-x-mark-line-right"];n<o.length;n++)ct(t,o[n],"backgroundColor",e.iconColor);ct(t,".swal2-success-ring","borderColor",e.iconColor)}},Zt=function(t){return'<div class="'.concat($["icon-content"],'">').concat(t,"</div>")},Qt=[],Jt=function(t,e){t.className="".concat($.popup," ").concat(wt(t)?e.showClass.popup:""),e.toast?(vt([document.documentElement,document.body],$["toast-shown"]),vt(t,$.toast)):vt(t,$.modal),z(t,e,"popup"),"string"==typeof e.customClass&&vt(t,e.customClass),e.icon&&vt(t,$["icon-".concat(e.icon)])};function $t(t){var e=x();e||Mn.fire(),e=x();var n=I(),o=D();!t&&wt(L())&&(t=L()),rt(n),t&&(at(t),o.setAttribute("data-button-to-replace",t.className)),o.parentNode.insertBefore(o,t),vt([e,n],$.loading),rt(o),e.setAttribute("data-loading",!0),e.setAttribute("aria-busy",!0),e.focus()}function Xt(){return new Promise(function(t){var e=window.scrollX,n=window.scrollY;ee.restoreFocusTimeout=setTimeout(function(){ee.previousActiveElement&&ee.previousActiveElement.focus?(ee.previousActiveElement.focus(),ee.previousActiveElement=null):document.body&&document.body.focus(),t()},100),void 0!==e&&void 0!==n&&window.scrollTo(e,n)})}function Gt(){if(ee.timeout)return function(){var t=V(),e=parseInt(window.getComputedStyle(t).width);t.style.removeProperty("transition"),t.style.width="100%";var n=parseInt(window.getComputedStyle(t).width),n=parseInt(e/n*100);t.style.removeProperty("transition"),t.style.width="".concat(n,"%")}(),ee.timeout.stop()}function te(){if(ee.timeout){var t=ee.timeout.start();return dt(t),t}}var ee={},ne=!1,oe={};function ie(t){for(var e=t.target;e&&e!==document;e=e.parentNode)for(var n in oe){var o=e.getAttribute(n);if(o)return void oe[n].fire({template:o})}}function re(t){return Object.prototype.hasOwnProperty.call(se,t)}function ae(t){return le[t]}function ce(t){for(var e in t)re(o=e)||W('Unknown parameter "'.concat(o,'"')),t.toast&&(n=e,-1!==de.indexOf(n)&&W('The parameter "'.concat(n,'" is incompatible with toasts'))),ae(n=e)&&v(n,ae(n));var n,o}var se={title:"",titleText:"",text:"",html:"",footer:"",icon:void 0,iconColor:void 0,iconHtml:void 0,template:void 0,toast:!1,animation:!0,showClass:{popup:"swal2-show",backdrop:"swal2-backdrop-show",icon:"swal2-icon-show"},hideClass:{popup:"swal2-hide",backdrop:"swal2-backdrop-hide",icon:"swal2-icon-hide"},customClass:{},target:"body",backdrop:!0,heightAuto:!0,allowOutsideClick:!0,allowEscapeKey:!0,allowEnterKey:!0,stopKeydownPropagation:!0,keydownListenerCapture:!1,showConfirmButton:!0,showDenyButton:!1,showCancelButton:!1,preConfirm:void 0,preDeny:void 0,confirmButtonText:"OK",confirmButtonAriaLabel:"",confirmButtonColor:void 0,denyButtonText:"No",denyButtonAriaLabel:"",denyButtonColor:void 0,cancelButtonText:"Cancel",cancelButtonAriaLabel:"",cancelButtonColor:void 0,buttonsStyling:!0,reverseButtons:!1,focusConfirm:!0,focusDeny:!1,focusCancel:!1,showCloseButton:!1,closeButtonHtml:"×",closeButtonAriaLabel:"Close this dialog",loaderHtml:"",showLoaderOnConfirm:!1,showLoaderOnDeny:!1,imageUrl:void 0,imageWidth:void 0,imageHeight:void 0,imageAlt:"",timer:void 0,timerProgressBar:!1,width:void 0,padding:void 0,background:void 0,input:void 0,inputPlaceholder:"",inputLabel:"",inputValue:"",inputOptions:{},inputAutoTrim:!0,inputAttributes:{},inputValidator:void 0,returnInputValueOnDeny:!1,validationMessage:void 0,grow:!1,position:"center",progressSteps:[],currentProgressStep:void 0,progressStepsDistance:void 0,onBeforeOpen:void 0,onOpen:void 0,willOpen:void 0,didOpen:void 0,onRender:void 0,didRender:void 0,onClose:void 0,onAfterClose:void 0,willClose:void 0,didClose:void 0,onDestroy:void 0,didDestroy:void 0,scrollbarPadding:!0},ue=["allowEscapeKey","allowOutsideClick","background","buttonsStyling","cancelButtonAriaLabel","cancelButtonColor","cancelButtonText","closeButtonAriaLabel","closeButtonHtml","confirmButtonAriaLabel","confirmButtonColor","confirmButtonText","currentProgressStep","customClass","denyButtonAriaLabel","denyButtonColor","denyButtonText","didClose","didDestroy","footer","hideClass","html","icon","iconColor","imageAlt","imageHeight","imageUrl","imageWidth","onAfterClose","onClose","onDestroy","progressSteps","reverseButtons","showCancelButton","showCloseButton","showConfirmButton","showDenyButton","text","title","titleText","willClose"],le={animation:'showClass" and "hideClass',onBeforeOpen:"willOpen",onOpen:"didOpen",onRender:"didRender",onClose:"willClose",onAfterClose:"didClose",onDestroy:"didDestroy"},de=["allowOutsideClick","allowEnterKey","backdrop","focusConfirm","focusDeny","focusCancel","heightAuto","keydownListenerCapture"],pe=Object.freeze({isValidParameter:re,isUpdatableParameter:function(t){return-1!==ue.indexOf(t)},isDeprecatedParameter:ae,argsToParams:function(n){var o={};return"object"!==r(n[0])||C(n[0])?["title","html","icon"].forEach(function(t,e){e=n[e];"string"==typeof e||C(e)?o[t]=e:void 0!==e&&K("Unexpected type of ".concat(t,'! Expected "string" or "Element", got ').concat(r(e)))}):s(o,n[0]),o},isVisible:function(){return wt(x())},clickConfirm:_t,clickDeny:function(){return q()&&q().click()},clickCancel:function(){return j()&&j().click()},getContainer:k,getPopup:x,getTitle:P,getContent:E,getHtmlContainer:function(){return t($["html-container"])},getImage:O,getIcon:B,getIcons:n,getInputLabel:function(){return t($["input-label"])},getCloseButton:R,getActions:I,getConfirmButton:L,getDenyButton:q,getCancelButton:j,getLoader:D,getHeader:M,getFooter:H,getTimerProgressBar:V,getFocusableElements:N,getValidationMessage:T,isLoading:function(){return x().hasAttribute("data-loading")},fire:function(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];return i(this,e)},mixin:function(r){return function(t){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&l(t,e)}(i,t);var n,o,e=(n=i,o=d(),function(){var t,e=u(n);return p(this,o?(t=u(this).constructor,Reflect.construct(e,arguments,t)):e.apply(this,arguments))});function i(){return a(this,i),e.apply(this,arguments)}return c(i,[{key:"_main",value:function(t,e){return f(u(i.prototype),"_main",this).call(this,t,s({},e,r))}}]),i}(this)},queue:function(t){var r=this;Qt=t;function a(t,e){Qt=[],t(e)}var c=[];return new Promise(function(i){!function e(n,o){n<Qt.length?(document.body.setAttribute("data-swal2-queue-step",n),r.fire(Qt[n]).then(function(t){void 0!==t.value?(c.push(t.value),e(n+1,o)):a(i,{dismiss:t.dismiss})})):a(i,{value:c})}(0)})},getQueueStep:Vt,insertQueueStep:function(t,e){return e&&e<Qt.length?Qt.splice(e,0,t):Qt.push(t)},deleteQueueStep:function(t){void 0!==Qt[t]&&Qt.splice(t,1)},showLoading:$t,enableLoading:$t,getTimerLeft:function(){return ee.timeout&&ee.timeout.getTimerLeft()},stopTimer:Gt,resumeTimer:te,toggleTimer:function(){var t=ee.timeout;return t&&(t.running?Gt:te)()},increaseTimer:function(t){if(ee.timeout){t=ee.timeout.increase(t);return dt(t,!0),t}},isTimerRunning:function(){return ee.timeout&&ee.timeout.isRunning()},bindClickHandler:function(){oe[0<arguments.length&&void 0!==arguments[0]?arguments[0]:"data-swal-template"]=this,ne||(document.body.addEventListener("click",ie),ne=!0)}});function fe(){var t,e;Tt.innerParams.get(this)&&(t=Tt.domCache.get(this),at(t.loader),(e=t.popup.getElementsByClassName(t.loader.getAttribute("data-button-to-replace"))).length?rt(e[0],"inline-block"):wt(L())||wt(q())||wt(j())||at(t.actions),bt([t.popup,t.actions],$.loading),t.popup.removeAttribute("aria-busy"),t.popup.removeAttribute("data-loading"),t.confirmButton.disabled=!1,t.denyButton.disabled=!1,t.cancelButton.disabled=!1)}function me(){null===tt.previousBodyPadding&&document.body.scrollHeight>window.innerHeight&&(tt.previousBodyPadding=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right")),document.body.style.paddingRight="".concat(tt.previousBodyPadding+function(){var t=document.createElement("div");t.className=$["scrollbar-measure"],document.body.appendChild(t);var e=t.getBoundingClientRect().width-t.clientWidth;return document.body.removeChild(t),e}(),"px"))}function he(){return!!window.MSInputMethodContext&&!!document.documentMode}function ge(){var t=k(),e=x();t.style.removeProperty("align-items"),e.offsetTop<0&&(t.style.alignItems="flex-start")}var ve=function(){navigator.userAgent.match(/(CriOS|FxiOS|EdgiOS|YaBrowser|UCBrowser)/i)||x().scrollHeight>window.innerHeight-44&&(k().style.paddingBottom="".concat(44,"px"))},be=function(){var e,t=k();t.ontouchstart=function(t){e=ye(t)},t.ontouchmove=function(t){e&&(t.preventDefault(),t.stopPropagation())}},ye=function(t){var e=t.target,n=k();return!we(t)&&!Ce(t)&&(e===n||!(ut(n)||"INPUT"===e.tagName||ut(E())&&E().contains(e)))},we=function(t){return t.touches&&t.touches.length&&"stylus"===t.touches[0].touchType},Ce=function(t){return t.touches&&1<t.touches.length},ke={swalPromiseResolve:new WeakMap};function Ae(t,e,n,o){n?Se(t,o):(Xt().then(function(){return Se(t,o)}),ee.keydownTarget.removeEventListener("keydown",ee.keydownHandler,{capture:ee.keydownListenerCapture}),ee.keydownHandlerAdded=!1),e.parentNode&&!document.body.getAttribute("data-swal2-queue-step")&&e.parentNode.removeChild(e),U()&&(null!==tt.previousBodyPadding&&(document.body.style.paddingRight="".concat(tt.previousBodyPadding,"px"),tt.previousBodyPadding=null),F(document.body,$.iosfix)&&(e=parseInt(document.body.style.top,10),bt(document.body,$.iosfix),document.body.style.top="",document.body.scrollTop=-1*e),"undefined"!=typeof window&&he()&&window.removeEventListener("resize",ge),g(document.body.children).forEach(function(t){t.hasAttribute("data-previous-aria-hidden")?(t.setAttribute("aria-hidden",t.getAttribute("data-previous-aria-hidden")),t.removeAttribute("data-previous-aria-hidden")):t.removeAttribute("aria-hidden")})),bt([document.documentElement,document.body],[$.shown,$["height-auto"],$["no-backdrop"],$["toast-shown"],$["toast-column"]])}function xe(t){var e,n,o,i=x();i&&(t=Be(t),(e=Tt.innerParams.get(this))&&!F(i,e.hideClass.popup)&&(n=ke.swalPromiseResolve.get(this),bt(i,e.showClass.popup),vt(i,e.hideClass.popup),o=k(),bt(o,e.showClass.backdrop),vt(o,e.hideClass.backdrop),Pe(this,i,e),n(t)))}function Be(t){return void 0===t?{isConfirmed:!1,isDenied:!1,isDismissed:!0}:s({isConfirmed:!1,isDenied:!1,isDismissed:!1},t)}function Pe(t,e,n){var o=k(),i=Bt&<(e),r=n.onClose,a=n.onAfterClose,c=n.willClose,n=n.didClose;Ee(e,c,r),i?Oe(t,e,o,n||a):Ae(t,o,G(),n||a)}var Ee=function(t,e,n){null!==e&&"function"==typeof e?e(t):null!==n&&"function"==typeof n&&n(t)},Oe=function(t,e,n,o){ee.swalCloseEventFinishedCallback=Ae.bind(null,t,n,G(),o),e.addEventListener(Bt,function(t){t.target===e&&(ee.swalCloseEventFinishedCallback(),delete ee.swalCloseEventFinishedCallback)})},Se=function(t,e){setTimeout(function(){"function"==typeof e&&e(),t._destroy()})};function Te(t,e,n){var o=Tt.domCache.get(t);e.forEach(function(t){o[t].disabled=n})}function Le(t,e){if(!t)return!1;if("radio"===t.type)for(var n=t.parentNode.parentNode.querySelectorAll("input"),o=0;o<n.length;o++)n[o].disabled=e;else t.disabled=e}var qe=function(){function n(t,e){a(this,n),this.callback=t,this.remaining=e,this.running=!1,this.start()}return c(n,[{key:"start",value:function(){return this.running||(this.running=!0,this.started=new Date,this.id=setTimeout(this.callback,this.remaining)),this.remaining}},{key:"stop",value:function(){return this.running&&(this.running=!1,clearTimeout(this.id),this.remaining-=new Date-this.started),this.remaining}},{key:"increase",value:function(t){var e=this.running;return e&&this.stop(),this.remaining+=t,e&&this.start(),this.remaining}},{key:"getTimerLeft",value:function(){return this.running&&(this.stop(),this.start()),this.remaining}},{key:"isRunning",value:function(){return this.running}}]),n}(),De={email:function(t,e){return/^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,24}$/.test(t)?Promise.resolve():Promise.resolve(e||"Invalid email address")},url:function(t,e){return/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)$/.test(t)?Promise.resolve():Promise.resolve(e||"Invalid URL")}};function je(t){var e,n;(e=t).inputValidator||Object.keys(De).forEach(function(t){e.input===t&&(e.inputValidator=De[t])}),t.showLoaderOnConfirm&&!t.preConfirm&&W("showLoaderOnConfirm is set to true, but preConfirm is not defined.\nshowLoaderOnConfirm should be used together with preConfirm, see usage example:\nhttps://sweetalert2.github.io/#ajax-request"),t.animation=Z(t.animation),(n=t).target&&("string"!=typeof n.target||document.querySelector(n.target))&&("string"==typeof n.target||n.target.appendChild)||(W('Target parameter is not valid, defaulting to "body"'),n.target="body"),"string"==typeof t.title&&(t.title=t.title.split("\n").join("<br />")),kt(t)}function Ie(t){var e=k(),n=x();"function"==typeof t.willOpen?t.willOpen(n):"function"==typeof t.onBeforeOpen&&t.onBeforeOpen(n);var o=window.getComputedStyle(document.body).overflowY;$e(e,n,t),setTimeout(function(){Qe(e,n)},10),U()&&(Je(e,t.scrollbarPadding,o),g(document.body.children).forEach(function(t){t===k()||function(t,e){if("function"==typeof t.contains)return t.contains(e)}(t,k())||(t.hasAttribute("aria-hidden")&&t.setAttribute("data-previous-aria-hidden",t.getAttribute("aria-hidden")),t.setAttribute("aria-hidden","true"))})),G()||ee.previousActiveElement||(ee.previousActiveElement=document.activeElement),Ze(n,t),bt(e,$["no-transition"])}function Me(t){var e=x();t.target===e&&(t=k(),e.removeEventListener(Bt,Me),t.style.overflowY="auto")}function He(t,e){t.closePopup({isConfirmed:!0,value:e})}function Ve(t,e,n){var o=N();if(o.length)return(e+=n)===o.length?e=0:-1===e&&(e=o.length-1),o[e].focus();x().focus()}var Re=["swal-title","swal-html","swal-footer"],Ne=function(t){var n={};return g(t.querySelectorAll("swal-param")).forEach(function(t){Ye(t,["name","value"]);var e=t.getAttribute("name"),t=t.getAttribute("value");"boolean"==typeof se[e]&&"false"===t&&(t=!1),"object"===r(se[e])&&(t=JSON.parse(t)),n[e]=t}),n},Ue=function(t){var n={};return g(t.querySelectorAll("swal-button")).forEach(function(t){Ye(t,["type","color","aria-label"]);var e=t.getAttribute("type");n["".concat(e,"ButtonText")]=t.innerHTML,n["show".concat(m(e),"Button")]=!0,t.hasAttribute("color")&&(n["".concat(e,"ButtonColor")]=t.getAttribute("color")),t.hasAttribute("aria-label")&&(n["".concat(e,"ButtonAriaLabel")]=t.getAttribute("aria-label"))}),n},_e=function(t){var e={},t=t.querySelector("swal-image");return t&&(Ye(t,["src","width","height","alt"]),t.hasAttribute("src")&&(e.imageUrl=t.getAttribute("src")),t.hasAttribute("width")&&(e.imageWidth=t.getAttribute("width")),t.hasAttribute("height")&&(e.imageHeight=t.getAttribute("height")),t.hasAttribute("alt")&&(e.imageAlt=t.getAttribute("alt"))),e},Fe=function(t){var e={},t=t.querySelector("swal-icon");return t&&(Ye(t,["type","color"]),t.hasAttribute("type")&&(e.icon=t.getAttribute("type")),t.hasAttribute("color")&&(e.iconColor=t.getAttribute("color")),e.iconHtml=t.innerHTML),e},ze=function(t){var n={},e=t.querySelector("swal-input");e&&(Ye(e,["type","label","placeholder","value"]),n.input=e.getAttribute("type")||"text",e.hasAttribute("label")&&(n.inputLabel=e.getAttribute("label")),e.hasAttribute("placeholder")&&(n.inputPlaceholder=e.getAttribute("placeholder")),e.hasAttribute("value")&&(n.inputValue=e.getAttribute("value")));t=t.querySelectorAll("swal-input-option");return t.length&&(n.inputOptions={},g(t).forEach(function(t){Ye(t,["value"]);var e=t.getAttribute("value"),t=t.innerHTML;n.inputOptions[e]=t})),n},We=function(t,e){var n,o={};for(n in e){var i=e[n],r=t.querySelector(i);r&&(Ye(r,[]),o[i.replace(/^swal-/,"")]=r.innerHTML)}return o},Ke=function(e){var n=Re.concat(["swal-param","swal-button","swal-image","swal-icon","swal-input","swal-input-option"]);g(e.querySelectorAll("*")).forEach(function(t){t.parentNode===e&&(t=t.tagName.toLowerCase(),-1===n.indexOf(t)&&W("Unrecognized element <".concat(t,">")))})},Ye=function(e,n){g(e.attributes).forEach(function(t){-1===n.indexOf(t.name)&&W(['Unrecognized attribute "'.concat(t.name,'" on <').concat(e.tagName.toLowerCase(),">."),"".concat(n.length?"Allowed attributes are: ".concat(n.join(", ")):"To set the value, use HTML within the element.")])})},Ze=function(t,e){"function"==typeof e.didOpen?setTimeout(function(){return e.didOpen(t)}):"function"==typeof e.onOpen&&setTimeout(function(){return e.onOpen(t)})},Qe=function(t,e){Bt&<(e)?(t.style.overflowY="hidden",e.addEventListener(Bt,Me)):t.style.overflowY="auto"},Je=function(t,e,n){var o;(/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream||"MacIntel"===navigator.platform&&1<navigator.maxTouchPoints)&&!F(document.body,$.iosfix)&&(o=document.body.scrollTop,document.body.style.top="".concat(-1*o,"px"),vt(document.body,$.iosfix),be(),ve()),"undefined"!=typeof window&&he()&&(ge(),window.addEventListener("resize",ge)),e&&"hidden"!==n&&me(),setTimeout(function(){t.scrollTop=0})},$e=function(t,e,n){vt(t,n.showClass.backdrop),e.style.setProperty("opacity","0","important"),rt(e),setTimeout(function(){vt(e,n.showClass.popup),e.style.removeProperty("opacity")},10),vt([document.documentElement,document.body],$.shown),n.heightAuto&&n.backdrop&&!n.toast&&vt([document.documentElement,document.body],$["height-auto"])},Xe=function(t){return t.checked?1:0},Ge=function(t){return t.checked?t.value:null},tn=function(t){return t.files.length?null!==t.getAttribute("multiple")?t.files:t.files[0]:null},en=function(e,n){function o(t){return on[n.input](i,rn(t),n)}var i=E();b(n.inputOptions)||w(n.inputOptions)?($t(),y(n.inputOptions).then(function(t){e.hideLoading(),o(t)})):"object"===r(n.inputOptions)?o(n.inputOptions):K("Unexpected type of inputOptions! Expected object, Map or Promise, got ".concat(r(n.inputOptions)))},nn=function(e,n){var o=e.getInput();at(o),y(n.inputValue).then(function(t){o.value="number"===n.input?parseFloat(t)||0:"".concat(t),rt(o),o.focus(),e.hideLoading()}).catch(function(t){K("Error in inputValue promise: ".concat(t)),o.value="",rt(o),o.focus(),e.hideLoading()})},on={select:function(t,e,i){function o(t,e,n){var o=document.createElement("option");o.value=n,_(o,e),o.selected=an(n,i.inputValue),t.appendChild(o)}var r=yt(t,$.select);e.forEach(function(t){var e,n=t[0],t=t[1];Array.isArray(t)?((e=document.createElement("optgroup")).label=n,e.disabled=!1,r.appendChild(e),t.forEach(function(t){return o(e,t[1],t[0])})):o(r,t,n)}),r.focus()},radio:function(t,e,i){var r=yt(t,$.radio);e.forEach(function(t){var e=t[0],n=t[1],o=document.createElement("input"),t=document.createElement("label");o.type="radio",o.name=$.radio,o.value=e,an(e,i.inputValue)&&(o.checked=!0);e=document.createElement("span");_(e,n),e.className=$.label,t.appendChild(o),t.appendChild(e),r.appendChild(t)});e=r.querySelectorAll("input");e.length&&e[0].focus()}},rn=function n(o){var i=[];return"undefined"!=typeof Map&&o instanceof Map?o.forEach(function(t,e){"object"===r(t)&&(t=n(t)),i.push([e,t])}):Object.keys(o).forEach(function(t){var e=o[t];"object"===r(e)&&(e=n(e)),i.push([t,e])}),i},an=function(t,e){return e&&e.toString()===t.toString()},cn=function(t,e,n){var o=function(t,e){var n=t.getInput();if(!n)return null;switch(e.input){case"checkbox":return Xe(n);case"radio":return Ge(n);case"file":return tn(n);default:return e.inputAutoTrim?n.value.trim():n.value}}(t,e);e.inputValidator?sn(t,e,o):t.getInput().checkValidity()?("deny"===n?un:ln)(t,e,o):(t.enableButtons(),t.showValidationMessage(e.validationMessage))},sn=function(e,n,o){e.disableInput(),Promise.resolve().then(function(){return y(n.inputValidator(o,n.validationMessage))}).then(function(t){e.enableButtons(),e.enableInput(),t?e.showValidationMessage(t):ln(e,n,o)})},un=function(e,t,n){t.showLoaderOnDeny&&$t(q()),t.preDeny?Promise.resolve().then(function(){return y(t.preDeny(n,t.validationMessage))}).then(function(t){!1===t?e.hideLoading():e.closePopup({isDenied:!0,value:void 0===t?n:t})}):e.closePopup({isDenied:!0,value:n})},ln=function(e,t,n){t.showLoaderOnConfirm&&$t(),t.preConfirm?(e.resetValidationMessage(),Promise.resolve().then(function(){return y(t.preConfirm(n,t.validationMessage))}).then(function(t){wt(T())||!1===t?e.hideLoading():He(e,void 0===t?n:t)})):He(e,n)},dn=["ArrowRight","ArrowDown","Right","Down"],pn=["ArrowLeft","ArrowUp","Left","Up"],fn=["Escape","Esc"],mn=function(t,e,n){var o=Tt.innerParams.get(t);o.stopKeydownPropagation&&e.stopPropagation(),"Enter"===e.key?hn(t,e,o):"Tab"===e.key?gn(e,o):-1!==[].concat(dn,pn).indexOf(e.key)?vn(e.key):-1!==fn.indexOf(e.key)&&bn(e,o,n)},hn=function(t,e,n){e.isComposing||e.target&&t.getInput()&&e.target.outerHTML===t.getInput().outerHTML&&-1===["textarea","file"].indexOf(n.input)&&(_t(),e.preventDefault())},gn=function(t,e){for(var n=t.target,o=N(),i=-1,r=0;r<o.length;r++)if(n===o[r]){i=r;break}t.shiftKey?Ve(0,i,-1):Ve(0,i,1),t.stopPropagation(),t.preventDefault()},vn=function(t){-1!==[L(),q(),j()].indexOf(document.activeElement)&&(t=-1!==dn.indexOf(t)?"nextElementSibling":"previousElementSibling",(t=document.activeElement[t])&&t.focus())},bn=function(t,e,n){Z(e.allowEscapeKey)&&(t.preventDefault(),n(Q.esc))},yn=function(e,t,n){t.popup.onclick=function(){var t=Tt.innerParams.get(e);t.showConfirmButton||t.showDenyButton||t.showCancelButton||t.showCloseButton||t.timer||t.input||n(Q.close)}},wn=!1,Cn=function(e){e.popup.onmousedown=function(){e.container.onmouseup=function(t){e.container.onmouseup=void 0,t.target===e.container&&(wn=!0)}}},kn=function(e){e.container.onmousedown=function(){e.popup.onmouseup=function(t){e.popup.onmouseup=void 0,t.target!==e.popup&&!e.popup.contains(t.target)||(wn=!0)}}},An=function(n,o,i){o.container.onclick=function(t){var e=Tt.innerParams.get(n);wn?wn=!1:t.target===o.container&&Z(e.allowOutsideClick)&&i(Q.backdrop)}};function xn(t,e){var n=function(t){t="string"==typeof t.template?document.querySelector(t.template):t.template;if(!t)return{};t=t.content||t;return Ke(t),s(Ne(t),Ue(t),_e(t),Fe(t),ze(t),We(t,Re))}(t),o=s({},se.showClass,e.showClass,n.showClass,t.showClass),i=s({},se.hideClass,e.hideClass,n.hideClass,t.hideClass);return(n=s({},se,e,n,t)).showClass=o,n.hideClass=i,!1===t.animation&&(n.showClass={popup:"swal2-noanimation",backdrop:"swal2-noanimation"},n.hideClass={}),n}function Bn(a,c,s){return new Promise(function(t){function e(t){a.closePopup({isDismissed:!0,dismiss:t})}var n,o,i,r;ke.swalPromiseResolve.set(a,t),c.confirmButton.onclick=function(){return e=s,(t=a).disableButtons(),void(e.input?cn(t,e,"confirm"):ln(t,e,!0));var t,e},c.denyButton.onclick=function(){return e=s,(t=a).disableButtons(),void(e.returnInputValueOnDeny?cn(t,e,"deny"):un(t,e,!1));var t,e},c.cancelButton.onclick=function(){return t=e,a.disableButtons(),void t(Q.cancel);var t},c.closeButton.onclick=function(){return e(Q.close)},n=a,r=c,t=e,Tt.innerParams.get(n).toast?yn(n,r,t):(Cn(r),kn(r),An(n,r,t)),o=a,r=s,i=e,(t=ee).keydownTarget&&t.keydownHandlerAdded&&(t.keydownTarget.removeEventListener("keydown",t.keydownHandler,{capture:t.keydownListenerCapture}),t.keydownHandlerAdded=!1),r.toast||(t.keydownHandler=function(t){return mn(o,t,i)},t.keydownTarget=r.keydownListenerCapture?window:x(),t.keydownListenerCapture=r.keydownListenerCapture,t.keydownTarget.addEventListener("keydown",t.keydownHandler,{capture:t.keydownListenerCapture}),t.keydownHandlerAdded=!0),(s.toast&&(s.input||s.footer||s.showCloseButton)?vt:bt)(document.body,$["toast-column"]),r=a,"select"===(t=s).input||"radio"===t.input?en(r,t):-1!==["text","email","number","tel","textarea"].indexOf(t.input)&&(b(t.inputValue)||w(t.inputValue))&&nn(r,t),Ie(s),En(ee,s,e),On(c,s),setTimeout(function(){c.container.scrollTop=0})})}function Pn(t){var e={popup:x(),container:k(),content:E(),actions:I(),confirmButton:L(),denyButton:q(),cancelButton:j(),loader:D(),closeButton:R(),validationMessage:T(),progressSteps:S()};return Tt.domCache.set(t,e),e}var En=function(t,e,n){var o=V();at(o),e.timer&&(t.timeout=new qe(function(){n("timer"),delete t.timeout},e.timer),e.timerProgressBar&&(rt(o),setTimeout(function(){t.timeout&&t.timeout.running&&dt(e.timer)})))},On=function(t,e){if(!e.toast)return Z(e.allowEnterKey)?void(Sn(t,e)||Ve(0,-1,1)):Tn()},Sn=function(t,e){return e.focusDeny&&wt(t.denyButton)?(t.denyButton.focus(),!0):e.focusCancel&&wt(t.cancelButton)?(t.cancelButton.focus(),!0):!(!e.focusConfirm||!wt(t.confirmButton))&&(t.confirmButton.focus(),!0)},Tn=function(){document.activeElement&&"function"==typeof document.activeElement.blur&&document.activeElement.blur()};function Ln(t){"function"==typeof t.didDestroy?t.didDestroy():"function"==typeof t.onDestroy&&t.onDestroy()}function qn(t){delete t.params,delete ee.keydownHandler,delete ee.keydownTarget,jn(Tt),jn(ke)}var Dn,jn=function(t){for(var e in t)t[e]=new WeakMap},J=Object.freeze({hideLoading:fe,disableLoading:fe,getInput:function(t){var e=Tt.innerParams.get(t||this);return(t=Tt.domCache.get(t||this))?et(t.content,e.input):null},close:xe,closePopup:xe,closeModal:xe,closeToast:xe,enableButtons:function(){Te(this,["confirmButton","denyButton","cancelButton"],!1)},disableButtons:function(){Te(this,["confirmButton","denyButton","cancelButton"],!0)},enableInput:function(){return Le(this.getInput(),!1)},disableInput:function(){return Le(this.getInput(),!0)},showValidationMessage:function(t){var e=Tt.domCache.get(this),n=Tt.innerParams.get(this);_(e.validationMessage,t),e.validationMessage.className=$["validation-message"],n.customClass&&n.customClass.validationMessage&&vt(e.validationMessage,n.customClass.validationMessage),rt(e.validationMessage),(e=this.getInput())&&(e.setAttribute("aria-invalid",!0),e.setAttribute("aria-describedBy",$["validation-message"]),nt(e),vt(e,$.inputerror))},resetValidationMessage:function(){var t=Tt.domCache.get(this);t.validationMessage&&at(t.validationMessage),(t=this.getInput())&&(t.removeAttribute("aria-invalid"),t.removeAttribute("aria-describedBy"),bt(t,$.inputerror))},getProgressSteps:function(){return Tt.domCache.get(this).progressSteps},_main:function(t){var e=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{};return ce(s({},e,t)),ee.currentInstance&&ee.currentInstance._destroy(),ee.currentInstance=this,je(t=xn(t,e)),Object.freeze(t),ee.timeout&&(ee.timeout.stop(),delete ee.timeout),clearTimeout(ee.restoreFocusTimeout),e=Pn(this),Ut(this,t),Tt.innerParams.set(this,t),Bn(this,e,t)},update:function(e){var t=x(),n=Tt.innerParams.get(this);if(!t||F(t,n.hideClass.popup))return W("You're trying to update the closed or closing popup, that won't work. Use the update() method in preConfirm parameter or show a new popup.");var o={};Object.keys(e).forEach(function(t){Mn.isUpdatableParameter(t)?o[t]=e[t]:W('Invalid parameter to update: "'.concat(t,'". Updatable params are listed here: https://github.com/sweetalert2/sweetalert2/blob/master/src/utils/params.js\n\nIf you think this parameter should be updatable, request it here: https://github.com/sweetalert2/sweetalert2/issues/new?template=02_feature_request.md'))}),n=s({},n,o),Ut(this,n),Tt.innerParams.set(this,n),Object.defineProperties(this,{params:{value:s({},this.params,e),writable:!1,enumerable:!0}})},_destroy:function(){var t=Tt.domCache.get(this),e=Tt.innerParams.get(this);e&&(t.popup&&ee.swalCloseEventFinishedCallback&&(ee.swalCloseEventFinishedCallback(),delete ee.swalCloseEventFinishedCallback),ee.deferDisposalTimer&&(clearTimeout(ee.deferDisposalTimer),delete ee.deferDisposalTimer),Ln(e),qn(this))}}),In=function(){function i(){if(a(this,i),"undefined"!=typeof window){"undefined"==typeof Promise&&K("This package requires a Promise library, please include a shim to enable it in this browser (See: https://github.com/sweetalert2/sweetalert2/wiki/Migration-from-SweetAlert-to-SweetAlert2#1-ie-support)"),Dn=this;for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];var o=Object.freeze(this.constructor.argsToParams(e));Object.defineProperties(this,{params:{value:o,writable:!1,enumerable:!0,configurable:!0}});o=this._main(this.params);Tt.promise.set(this,o)}}return c(i,[{key:"then",value:function(t){return Tt.promise.get(this).then(t)}},{key:"finally",value:function(t){return Tt.promise.get(this).finally(t)}}]),i}();s(In.prototype,J),s(In,pe),Object.keys(J).forEach(function(t){In[t]=function(){if(Dn)return Dn[t].apply(Dn,arguments)}}),In.DismissReason=Q,In.version="10.14.1";var Mn=In;return Mn.default=Mn}),void 0!==this&&this.Sweetalert2&&(this.swal=this.sweetAlert=this.Swal=this.SweetAlert=this.Sweetalert2); +"undefined"!=typeof document&&function(e,t){var n=e.createElement("style");if(e.getElementsByTagName("head")[0].appendChild(n),n.styleSheet)n.styleSheet.disabled||(n.styleSheet.cssText=t);else try{n.innerHTML=t}catch(e){n.innerText=t}}(document,".swal2-popup.swal2-toast{flex-direction:row;align-items:center;width:auto;padding:.625em;overflow-y:hidden;background:#fff;box-shadow:0 0 .625em #d9d9d9}.swal2-popup.swal2-toast .swal2-header{flex-direction:row;padding:0}.swal2-popup.swal2-toast .swal2-title{flex-grow:1;justify-content:flex-start;margin:0 .6em;font-size:1em}.swal2-popup.swal2-toast .swal2-footer{margin:.5em 0 0;padding:.5em 0 0;font-size:.8em}.swal2-popup.swal2-toast .swal2-close{position:static;width:.8em;height:.8em;line-height:.8}.swal2-popup.swal2-toast .swal2-content{justify-content:flex-start;padding:0;font-size:1em}.swal2-popup.swal2-toast .swal2-icon{width:2em;min-width:2em;height:2em;margin:0}.swal2-popup.swal2-toast .swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:1.8em;font-weight:700}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-popup.swal2-toast .swal2-icon .swal2-icon-content{font-size:.25em}}.swal2-popup.swal2-toast .swal2-icon.swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line]{top:.875em;width:1.375em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:.3125em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:.3125em}.swal2-popup.swal2-toast .swal2-actions{flex-basis:auto!important;width:auto;height:auto;margin:0 .3125em;padding:0}.swal2-popup.swal2-toast .swal2-styled{margin:.125em .3125em;padding:.3125em .625em;font-size:1em}.swal2-popup.swal2-toast .swal2-styled:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px rgba(100,150,200,.5)}.swal2-popup.swal2-toast .swal2-success{border-color:#a5dc86}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line]{position:absolute;width:1.6em;height:3em;transform:rotate(45deg);border-radius:50%}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.8em;left:-.5em;transform:rotate(-45deg);transform-origin:2em 2em;border-radius:4em 0 0 4em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.25em;left:.9375em;transform-origin:0 1.5em;border-radius:0 4em 4em 0}.swal2-popup.swal2-toast .swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-success .swal2-success-fix{top:0;left:.4375em;width:.4375em;height:2.6875em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line]{height:.3125em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=tip]{top:1.125em;left:.1875em;width:.75em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=long]{top:.9375em;right:.1875em;width:1.375em}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-tip{-webkit-animation:swal2-toast-animate-success-line-tip .75s;animation:swal2-toast-animate-success-line-tip .75s}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-long{-webkit-animation:swal2-toast-animate-success-line-long .75s;animation:swal2-toast-animate-success-line-long .75s}.swal2-popup.swal2-toast.swal2-show{-webkit-animation:swal2-toast-show .5s;animation:swal2-toast-show .5s}.swal2-popup.swal2-toast.swal2-hide{-webkit-animation:swal2-toast-hide .1s forwards;animation:swal2-toast-hide .1s forwards}.swal2-container{display:flex;position:fixed;z-index:1060;top:0;right:0;bottom:0;left:0;flex-direction:row;align-items:center;justify-content:center;padding:.625em;overflow-x:hidden;transition:background-color .1s;-webkit-overflow-scrolling:touch}.swal2-container.swal2-backdrop-show,.swal2-container.swal2-noanimation{background:rgba(0,0,0,.4)}.swal2-container.swal2-backdrop-hide{background:0 0!important}.swal2-container.swal2-top{align-items:flex-start}.swal2-container.swal2-top-left,.swal2-container.swal2-top-start{align-items:flex-start;justify-content:flex-start}.swal2-container.swal2-top-end,.swal2-container.swal2-top-right{align-items:flex-start;justify-content:flex-end}.swal2-container.swal2-center{align-items:center}.swal2-container.swal2-center-left,.swal2-container.swal2-center-start{align-items:center;justify-content:flex-start}.swal2-container.swal2-center-end,.swal2-container.swal2-center-right{align-items:center;justify-content:flex-end}.swal2-container.swal2-bottom{align-items:flex-end}.swal2-container.swal2-bottom-left,.swal2-container.swal2-bottom-start{align-items:flex-end;justify-content:flex-start}.swal2-container.swal2-bottom-end,.swal2-container.swal2-bottom-right{align-items:flex-end;justify-content:flex-end}.swal2-container.swal2-bottom-end>:first-child,.swal2-container.swal2-bottom-left>:first-child,.swal2-container.swal2-bottom-right>:first-child,.swal2-container.swal2-bottom-start>:first-child,.swal2-container.swal2-bottom>:first-child{margin-top:auto}.swal2-container.swal2-grow-fullscreen>.swal2-modal{display:flex!important;flex:1;align-self:stretch;justify-content:center}.swal2-container.swal2-grow-row>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-grow-column{flex:1;flex-direction:column}.swal2-container.swal2-grow-column.swal2-bottom,.swal2-container.swal2-grow-column.swal2-center,.swal2-container.swal2-grow-column.swal2-top{align-items:center}.swal2-container.swal2-grow-column.swal2-bottom-left,.swal2-container.swal2-grow-column.swal2-bottom-start,.swal2-container.swal2-grow-column.swal2-center-left,.swal2-container.swal2-grow-column.swal2-center-start,.swal2-container.swal2-grow-column.swal2-top-left,.swal2-container.swal2-grow-column.swal2-top-start{align-items:flex-start}.swal2-container.swal2-grow-column.swal2-bottom-end,.swal2-container.swal2-grow-column.swal2-bottom-right,.swal2-container.swal2-grow-column.swal2-center-end,.swal2-container.swal2-grow-column.swal2-center-right,.swal2-container.swal2-grow-column.swal2-top-end,.swal2-container.swal2-grow-column.swal2-top-right{align-items:flex-end}.swal2-container.swal2-grow-column>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-no-transition{transition:none!important}.swal2-container:not(.swal2-top):not(.swal2-top-start):not(.swal2-top-end):not(.swal2-top-left):not(.swal2-top-right):not(.swal2-center-start):not(.swal2-center-end):not(.swal2-center-left):not(.swal2-center-right):not(.swal2-bottom):not(.swal2-bottom-start):not(.swal2-bottom-end):not(.swal2-bottom-left):not(.swal2-bottom-right):not(.swal2-grow-fullscreen)>.swal2-modal{margin:auto}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-container .swal2-modal{margin:0!important}}.swal2-popup{display:none;position:relative;box-sizing:border-box;flex-direction:column;justify-content:center;width:32em;max-width:100%;padding:1.25em;border:none;border-radius:5px;background:#fff;font-family:inherit;font-size:1rem}.swal2-popup:focus{outline:0}.swal2-popup.swal2-loading{overflow-y:hidden}.swal2-header{display:flex;flex-direction:column;align-items:center;padding:0 1.8em}.swal2-title{position:relative;max-width:100%;margin:0 0 .4em;padding:0;color:#595959;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word}.swal2-actions{display:flex;z-index:1;box-sizing:border-box;flex-wrap:wrap;align-items:center;justify-content:center;width:100%;margin:1.25em auto 0;padding:0 1.6em}.swal2-actions:not(.swal2-loading) .swal2-styled[disabled]{opacity:.4}.swal2-actions:not(.swal2-loading) .swal2-styled:hover{background-image:linear-gradient(rgba(0,0,0,.1),rgba(0,0,0,.1))}.swal2-actions:not(.swal2-loading) .swal2-styled:active{background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.2))}.swal2-loader{display:none;align-items:center;justify-content:center;width:2.2em;height:2.2em;margin:0 1.875em;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border-width:.25em;border-style:solid;border-radius:100%;border-color:#2778c4 transparent #2778c4 transparent}.swal2-styled{margin:.3125em;padding:.625em 1.1em;box-shadow:none;font-weight:500}.swal2-styled:not([disabled]){cursor:pointer}.swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:#2778c4;color:#fff;font-size:1.0625em}.swal2-styled.swal2-deny{border:0;border-radius:.25em;background:initial;background-color:#d14529;color:#fff;font-size:1.0625em}.swal2-styled.swal2-cancel{border:0;border-radius:.25em;background:initial;background-color:#757575;color:#fff;font-size:1.0625em}.swal2-styled:focus{outline:0;box-shadow:0 0 0 3px rgba(100,150,200,.5)}.swal2-styled::-moz-focus-inner{border:0}.swal2-footer{justify-content:center;margin:1.25em 0 0;padding:1em 0 0;border-top:1px solid #eee;color:#545454;font-size:1em}.swal2-timer-progress-bar-container{position:absolute;right:0;bottom:0;left:0;height:.25em;overflow:hidden;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.swal2-timer-progress-bar{width:100%;height:.25em;background:rgba(0,0,0,.2)}.swal2-image{max-width:100%;margin:1.25em auto}.swal2-close{position:absolute;z-index:2;top:0;right:0;align-items:center;justify-content:center;width:1.2em;height:1.2em;padding:0;overflow:hidden;transition:color .1s ease-out;border:none;border-radius:5px;background:0 0;color:#ccc;font-family:serif;font-size:2.5em;line-height:1.2;cursor:pointer}.swal2-close:hover{transform:none;background:0 0;color:#f27474}.swal2-close:focus{outline:0;box-shadow:inset 0 0 0 3px rgba(100,150,200,.5)}.swal2-close::-moz-focus-inner{border:0}.swal2-content{z-index:1;justify-content:center;margin:0;padding:0 1.6em;color:#545454;font-size:1.125em;font-weight:400;line-height:normal;text-align:center;word-wrap:break-word}.swal2-checkbox,.swal2-file,.swal2-input,.swal2-radio,.swal2-select,.swal2-textarea{margin:1em auto}.swal2-file,.swal2-input,.swal2-textarea{box-sizing:border-box;width:100%;transition:border-color .3s,box-shadow .3s;border:1px solid #d9d9d9;border-radius:.1875em;background:inherit;box-shadow:inset 0 1px 1px rgba(0,0,0,.06);color:inherit;font-size:1.125em}.swal2-file.swal2-inputerror,.swal2-input.swal2-inputerror,.swal2-textarea.swal2-inputerror{border-color:#f27474!important;box-shadow:0 0 2px #f27474!important}.swal2-file:focus,.swal2-input:focus,.swal2-textarea:focus{border:1px solid #b4dbed;outline:0;box-shadow:0 0 0 3px rgba(100,150,200,.5)}.swal2-file::-moz-placeholder,.swal2-input::-moz-placeholder,.swal2-textarea::-moz-placeholder{color:#ccc}.swal2-file:-ms-input-placeholder,.swal2-input:-ms-input-placeholder,.swal2-textarea:-ms-input-placeholder{color:#ccc}.swal2-file::placeholder,.swal2-input::placeholder,.swal2-textarea::placeholder{color:#ccc}.swal2-range{margin:1em auto;background:#fff}.swal2-range input{width:80%}.swal2-range output{width:20%;color:inherit;font-weight:600;text-align:center}.swal2-range input,.swal2-range output{height:2.625em;padding:0;font-size:1.125em;line-height:2.625em}.swal2-input{height:2.625em;padding:0 .75em}.swal2-input[type=number]{max-width:10em}.swal2-file{background:inherit;font-size:1.125em}.swal2-textarea{height:6.75em;padding:.75em}.swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:inherit;color:inherit;font-size:1.125em}.swal2-checkbox,.swal2-radio{align-items:center;justify-content:center;background:#fff;color:inherit}.swal2-checkbox label,.swal2-radio label{margin:0 .6em;font-size:1.125em}.swal2-checkbox input,.swal2-radio input{margin:0 .4em}.swal2-input-label{display:flex;justify-content:center;margin:1em auto}.swal2-validation-message{display:none;align-items:center;justify-content:center;margin:0 -2.7em;padding:.625em;overflow:hidden;background:#f0f0f0;color:#666;font-size:1em;font-weight:300}.swal2-validation-message::before{content:\"!\";display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center}.swal2-icon{position:relative;box-sizing:content-box;justify-content:center;width:5em;height:5em;margin:1.25em auto 1.875em;border:.25em solid transparent;border-radius:50%;font-family:inherit;line-height:5em;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:3.75em}.swal2-icon.swal2-error{border-color:#f27474;color:#f27474}.swal2-icon.swal2-error .swal2-x-mark{position:relative;flex-grow:1;zoom:1}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;transform:rotate(45deg)}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;transform:rotate(-45deg)}.swal2-icon.swal2-error.swal2-icon-show{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-icon.swal2-error.swal2-icon-show .swal2-x-mark{-webkit-animation:swal2-animate-error-x-mark .5s;animation:swal2-animate-error-x-mark .5s}.swal2-icon.swal2-warning{border-color:#facea8;color:#f8bb86}.swal2-icon.swal2-info{border-color:#9de0f6;color:#3fc3ee}.swal2-icon.swal2-question{border-color:#c9dae1;color:#87adbd}.swal2-icon.swal2-success{border-color:#a5dc86;color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;transform:rotate(45deg);border-radius:50%}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.4375em;left:-2.0635em;zoom:1;transform:rotate(-45deg);transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.6875em;left:1.875em;zoom:1;transform:rotate(-45deg);transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}.swal2-icon.swal2-success .swal2-success-ring{position:absolute;z-index:2;top:-.25em;left:-.25em;box-sizing:content-box;width:100%;height:100%;zoom:1;border:.25em solid rgba(165,220,134,.3);border-radius:50%}.swal2-icon.swal2-success .swal2-success-fix{position:absolute;z-index:1;top:.5em;left:1.625em;width:.4375em;height:5.625em;zoom:1;transform:rotate(-45deg)}.swal2-icon.swal2-success [class^=swal2-success-line]{display:block;position:absolute;z-index:2;height:.3125em;zoom:1;border-radius:.125em;background-color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.8125em;width:1.5625em;transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;transform:rotate(-45deg)}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-tip{-webkit-animation:swal2-animate-success-line-tip .75s;animation:swal2-animate-success-line-tip .75s}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-long{-webkit-animation:swal2-animate-success-line-long .75s;animation:swal2-animate-success-line-long .75s}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-circular-line-right{-webkit-animation:swal2-rotate-success-circular-line 4.25s ease-in;animation:swal2-rotate-success-circular-line 4.25s ease-in}.swal2-progress-steps{flex-wrap:wrap;align-items:center;max-width:100%;margin:0 0 1.25em;padding:0;background:inherit;font-weight:600}.swal2-progress-steps li{display:inline-block;position:relative}.swal2-progress-steps .swal2-progress-step{z-index:20;flex-shrink:0;width:2em;height:2em;border-radius:2em;background:#2778c4;color:#fff;line-height:2em;text-align:center}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:#2778c4}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step{background:#add8e6;color:#fff}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line{background:#add8e6}.swal2-progress-steps .swal2-progress-step-line{z-index:10;flex-shrink:0;width:2.5em;height:.4em;margin:0 -1px;background:#2778c4}[class^=swal2]{-webkit-tap-highlight-color:transparent}.swal2-show{-webkit-animation:swal2-show .3s;animation:swal2-show .3s}.swal2-hide{-webkit-animation:swal2-hide .15s forwards;animation:swal2-hide .15s forwards}.swal2-noanimation{transition:none}.swal2-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}.swal2-rtl .swal2-close{right:auto;left:0}.swal2-rtl .swal2-timer-progress-bar{right:0;left:auto}@supports (-ms-accelerator:true){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@-webkit-keyframes swal2-toast-show{0%{transform:translateY(-.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0)}}@keyframes swal2-toast-show{0%{transform:translateY(-.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0)}}@-webkit-keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@-webkit-keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@-webkit-keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@-webkit-keyframes swal2-show{0%{transform:scale(.7)}45%{transform:scale(1.05)}80%{transform:scale(.95)}100%{transform:scale(1)}}@keyframes swal2-show{0%{transform:scale(.7)}45%{transform:scale(1.05)}80%{transform:scale(.95)}100%{transform:scale(1)}}@-webkit-keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(.5);opacity:0}}@keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(.5);opacity:0}}@-webkit-keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@-webkit-keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@-webkit-keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@-webkit-keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(.4);opacity:0}50%{margin-top:1.625em;transform:scale(.4);opacity:0}80%{margin-top:-.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(.4);opacity:0}50%{margin-top:1.625em;transform:scale(.4);opacity:0}80%{margin-top:-.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@-webkit-keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);opacity:1}}@keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);opacity:1}}@-webkit-keyframes swal2-rotate-loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes swal2-rotate-loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow:hidden}body.swal2-height-auto{height:auto!important}body.swal2-no-backdrop .swal2-container{top:auto;right:auto;bottom:auto;left:auto;max-width:calc(100% - .625em * 2);background-color:transparent!important}body.swal2-no-backdrop .swal2-container>.swal2-modal{box-shadow:0 0 10px rgba(0,0,0,.4)}body.swal2-no-backdrop .swal2-container.swal2-top{top:0;left:50%;transform:translateX(-50%)}body.swal2-no-backdrop .swal2-container.swal2-top-left,body.swal2-no-backdrop .swal2-container.swal2-top-start{top:0;left:0}body.swal2-no-backdrop .swal2-container.swal2-top-end,body.swal2-no-backdrop .swal2-container.swal2-top-right{top:0;right:0}body.swal2-no-backdrop .swal2-container.swal2-center{top:50%;left:50%;transform:translate(-50%,-50%)}body.swal2-no-backdrop .swal2-container.swal2-center-left,body.swal2-no-backdrop .swal2-container.swal2-center-start{top:50%;left:0;transform:translateY(-50%)}body.swal2-no-backdrop .swal2-container.swal2-center-end,body.swal2-no-backdrop .swal2-container.swal2-center-right{top:50%;right:0;transform:translateY(-50%)}body.swal2-no-backdrop .swal2-container.swal2-bottom{bottom:0;left:50%;transform:translateX(-50%)}body.swal2-no-backdrop .swal2-container.swal2-bottom-left,body.swal2-no-backdrop .swal2-container.swal2-bottom-start{bottom:0;left:0}body.swal2-no-backdrop .swal2-container.swal2-bottom-end,body.swal2-no-backdrop .swal2-container.swal2-bottom-right{right:0;bottom:0}@media print{body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow-y:scroll!important}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container{position:static!important}}body.swal2-toast-shown .swal2-container{background-color:transparent}body.swal2-toast-shown .swal2-container.swal2-top{top:0;right:auto;bottom:auto;left:50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-top-end,body.swal2-toast-shown .swal2-container.swal2-top-right{top:0;right:0;bottom:auto;left:auto}body.swal2-toast-shown .swal2-container.swal2-top-left,body.swal2-toast-shown .swal2-container.swal2-top-start{top:0;right:auto;bottom:auto;left:0}body.swal2-toast-shown .swal2-container.swal2-center-left,body.swal2-toast-shown .swal2-container.swal2-center-start{top:50%;right:auto;bottom:auto;left:0;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-center{top:50%;right:auto;bottom:auto;left:50%;transform:translate(-50%,-50%)}body.swal2-toast-shown .swal2-container.swal2-center-end,body.swal2-toast-shown .swal2-container.swal2-center-right{top:50%;right:0;bottom:auto;left:auto;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-left,body.swal2-toast-shown .swal2-container.swal2-bottom-start{top:auto;right:auto;bottom:0;left:0}body.swal2-toast-shown .swal2-container.swal2-bottom{top:auto;right:auto;bottom:0;left:50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-end,body.swal2-toast-shown .swal2-container.swal2-bottom-right{top:auto;right:0;bottom:0;left:auto}body.swal2-toast-column .swal2-toast{flex-direction:column;align-items:stretch}body.swal2-toast-column .swal2-toast .swal2-actions{flex:1;align-self:stretch;height:2.2em;margin-top:.3125em}body.swal2-toast-column .swal2-toast .swal2-loading{justify-content:center}body.swal2-toast-column .swal2-toast .swal2-input{height:2em;margin:.3125em auto;font-size:1em}body.swal2-toast-column .swal2-toast .swal2-validation-message{font-size:1em}"); \ No newline at end of file diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 9510a2392..30fb8eafc 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -472,7 +472,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <!-- ./wrapper --> <!-- Scripts --> - <script src="https://cdn.jsdelivr.net/npm/sweetalert2@10.14.1/dist/sweetalert2.all.min.js"></script> + <script src="{{ asset('plugins/sweetalert2/sweetalert2.all.min.js') }}"></script> <script src="{{ asset('plugins/datatables/jquery.dataTables.min.js') }}"></script> <!-- Summernote --> From 0bde32bd2fac84b315506539309d7875ce67a8c9 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 00:17:40 +0200 Subject: [PATCH 100/514] remove encryption to continue working // FIX INSTALLER [value] -> [payload] --- .../PaymentGateways/Mollie/MollieSettings.php | 7 +------ ...23_03_26_215801_create_mollie_settings.php | 2 +- .../PaymentGateways/PayPal/PayPalSettings.php | 10 +--------- ...3_03_04_135248_create_pay_pal_settings.php | 8 ++++---- .../PaymentGateways/Stripe/StripeSettings.php | 10 +--------- ...23_03_04_181917_create_stripe_settings.php | 8 ++++---- app/Settings/DiscordSettings.php | 9 +-------- app/Settings/GeneralSettings.php | 8 +------- app/Settings/MailSettings.php | 7 +------ app/Settings/PterodactylSettings.php | 8 +------- ...3_02_01_164731_create_general_settings.php | 4 ++-- ..._01_181334_create_pterodactyl_settings.php | 4 ++-- ...2023_02_01_181453_create_mail_settings.php | 2 +- ...3_02_01_182043_create_discord_settings.php | 6 +++--- public/install/forms.php | 19 ++++++++++--------- public/install/functions.php | 4 ++++ 16 files changed, 38 insertions(+), 78 deletions(-) diff --git a/app/Extensions/PaymentGateways/Mollie/MollieSettings.php b/app/Extensions/PaymentGateways/Mollie/MollieSettings.php index d98658f18..a551b4a52 100644 --- a/app/Extensions/PaymentGateways/Mollie/MollieSettings.php +++ b/app/Extensions/PaymentGateways/Mollie/MollieSettings.php @@ -15,12 +15,7 @@ public static function group(): string return 'mollie'; } - public static function encrypted(): array - { - return [ - 'api_key', - ]; - } + public static function getOptionInputData() { diff --git a/app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php b/app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php index a3b6bfd04..b32027e30 100644 --- a/app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php +++ b/app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php @@ -6,7 +6,7 @@ class CreateMollieSettings extends SettingsMigration { public function up(): void { - $this->migrator->addEncrypted('mollie.api_key', null); + $this->migrator->add('mollie.api_key', null); $this->migrator->add('mollie.enabled', false); } diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalSettings.php b/app/Extensions/PaymentGateways/PayPal/PayPalSettings.php index 1688c011b..9d22dec26 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalSettings.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalSettings.php @@ -18,15 +18,7 @@ public static function group(): string } - public static function encrypted(): array - { - return [ - 'client_id', - 'client_secret', - 'sandbox_client_id', - 'sandbox_client_secret' - ]; - } + /** * Summary of optionInputData array diff --git a/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php b/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php index 5792abec4..9dc4887cf 100644 --- a/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php +++ b/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php @@ -11,10 +11,10 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); - $this->migrator->addEncrypted('paypal.client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID') : null); - $this->migrator->addEncrypted('paypal.client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SECRET') : null); - $this->migrator->addEncrypted('paypal.sandbox_client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID') : null); - $this->migrator->addEncrypted('paypal.sandbox_client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET') : null); + $this->migrator->add('paypal.client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID') : null); + $this->migrator->add('paypal.client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SECRET') : null); + $this->migrator->add('paypal.sandbox_client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID') : null); + $this->migrator->add('paypal.sandbox_client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET') : null); $this->migrator->add('paypal.enabled', false); } diff --git a/app/Extensions/PaymentGateways/Stripe/StripeSettings.php b/app/Extensions/PaymentGateways/Stripe/StripeSettings.php index c7a2d7bd9..7cf6de5f6 100644 --- a/app/Extensions/PaymentGateways/Stripe/StripeSettings.php +++ b/app/Extensions/PaymentGateways/Stripe/StripeSettings.php @@ -19,15 +19,7 @@ public static function group(): string return 'stripe'; } - public static function encrypted(): array - { - return [ - "secret_key", - "endpoint_secret", - "test_secret_key", - "test_endpoint_secret" - ]; - } + public static function getOptionInputData() { diff --git a/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php b/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php index a8145a7c8..c3588d269 100644 --- a/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php +++ b/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php @@ -9,10 +9,10 @@ public function up(): void { $table_exists = DB::table('settings_old')->exists(); - $this->migrator->addEncrypted('stripe.secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:SECRET') : null); - $this->migrator->addEncrypted('stripe.endpoint_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET') : null); - $this->migrator->addEncrypted('stripe.test_secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET') : null); - $this->migrator->addEncrypted('stripe.test_endpoint_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET') : null); + $this->migrator->add('stripe.secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:SECRET') : null); + $this->migrator->add('stripe.endpoint_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET') : null); + $this->migrator->add('stripe.test_secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET') : null); + $this->migrator->add('stripe.test_endpoint_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET') : null); $this->migrator->add('stripe.enabled', false); } diff --git a/app/Settings/DiscordSettings.php b/app/Settings/DiscordSettings.php index 8671fa8af..4788ae7eb 100644 --- a/app/Settings/DiscordSettings.php +++ b/app/Settings/DiscordSettings.php @@ -18,14 +18,7 @@ public static function group(): string return 'discord'; } - public static function encrypted(): array - { - return [ - 'bot_token', - 'client_id', - 'client_secret' - ]; - } + /** * Summary of validations array diff --git a/app/Settings/GeneralSettings.php b/app/Settings/GeneralSettings.php index 02f8767f3..8aa91e222 100644 --- a/app/Settings/GeneralSettings.php +++ b/app/Settings/GeneralSettings.php @@ -24,13 +24,7 @@ public static function group(): string return 'general'; } - public static function encrypted(): array - { - return [ - 'recaptcha_site_key', - 'recaptcha_secret_key' - ]; - } + /** * Summary of validations array diff --git a/app/Settings/MailSettings.php b/app/Settings/MailSettings.php index e94953ceb..cd70a7fa4 100644 --- a/app/Settings/MailSettings.php +++ b/app/Settings/MailSettings.php @@ -21,12 +21,7 @@ public static function group(): string return 'mail'; } - public static function encrypted(): array - { - return [ - 'mail_password' - ]; - } + public function setConfig() { diff --git a/app/Settings/PterodactylSettings.php b/app/Settings/PterodactylSettings.php index 2267152e7..3e888b937 100644 --- a/app/Settings/PterodactylSettings.php +++ b/app/Settings/PterodactylSettings.php @@ -16,13 +16,7 @@ public static function group(): string return 'pterodactyl'; } - public static function encrypted(): array - { - return [ - 'admin_token', - 'user_token' - ]; - } + /** * Get url with ensured ending backslash diff --git a/database/settings/2023_02_01_164731_create_general_settings.php b/database/settings/2023_02_01_164731_create_general_settings.php index 8962889ae..5d1835907 100644 --- a/database/settings/2023_02_01_164731_create_general_settings.php +++ b/database/settings/2023_02_01_164731_create_general_settings.php @@ -12,8 +12,8 @@ public function up(): void // Get the user-set configuration values from the old table. $this->migrator->add('general.store_enabled', true); $this->migrator->add('general.credits_display_name', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME') : 'Credits'); - $this->migrator->addEncrypted('general.recaptcha_site_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SITE_KEY") : env('RECAPTCHA_SITE_KEY', '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI')); - $this->migrator->addEncrypted('general.recaptcha_secret_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SECRET_KEY") : env('RECAPTCHA_SECRET_KEY', '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe')); + $this->migrator->add('general.recaptcha_site_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SITE_KEY") : env('RECAPTCHA_SITE_KEY', '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI')); + $this->migrator->add('general.recaptcha_secret_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SECRET_KEY") : env('RECAPTCHA_SECRET_KEY', '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe')); $this->migrator->add('general.recaptcha_enabled', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:ENABLED") : true); $this->migrator->add('general.phpmyadmin_url', $table_exists ? $this->getOldValue("SETTINGS::MISC:PHPMYADMIN:URL") : env('PHPMYADMIN_URL', '')); $this->migrator->add('general.alert_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_ENABLED") : false); diff --git a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php index f3b5b37d2..3de541759 100644 --- a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php +++ b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php @@ -10,8 +10,8 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); - $this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); + $this->migrator->add('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); + $this->migrator->add('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); $this->migrator->add('pterodactyl.panel_url', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:URL') : env('PTERODACTYL_URL', '')); $this->migrator->add('pterodactyl.per_page_limit', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT') : 200); } diff --git a/database/settings/2023_02_01_181453_create_mail_settings.php b/database/settings/2023_02_01_181453_create_mail_settings.php index 98fe06d0e..7a1717320 100644 --- a/database/settings/2023_02_01_181453_create_mail_settings.php +++ b/database/settings/2023_02_01_181453_create_mail_settings.php @@ -13,7 +13,7 @@ public function up(): void $this->migrator->add('mail.mail_host', $table_exists ? $this->getOldValue('SETTINGS::MAIL:HOST') : env('MAIL_HOST', 'localhost')); $this->migrator->add('mail.mail_port', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PORT') : env('MAIL_PORT', 25)); $this->migrator->add('mail.mail_username', $table_exists ? $this->getOldValue('SETTINGS::MAIL:USERNAME') : env('MAIL_USERNAME', '')); - $this->migrator->addEncrypted('mail.mail_password', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PASSWORD') : env('MAIL_PASSWORD', '')); + $this->migrator->add('mail.mail_password', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PASSWORD') : env('MAIL_PASSWORD', '')); $this->migrator->add('mail.mail_encryption', $table_exists ? $this->getOldValue('SETTINGS::MAIL:ENCRYPTION') : env('MAIL_ENCRYPTION', 'tls')); $this->migrator->add('mail.mail_from_address', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_ADDRESS') : env('MAIL_FROM_ADDRESS', 'example@example.com')); $this->migrator->add('mail.mail_from_name', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_NAME') : env('APP_NAME', 'ControlPanel.gg')); diff --git a/database/settings/2023_02_01_182043_create_discord_settings.php b/database/settings/2023_02_01_182043_create_discord_settings.php index 1f4335aa4..60e450b90 100644 --- a/database/settings/2023_02_01_182043_create_discord_settings.php +++ b/database/settings/2023_02_01_182043_create_discord_settings.php @@ -10,9 +10,9 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->addEncrypted('discord.bot_token', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:BOT_TOKEN') : ''); - $this->migrator->addEncrypted('discord.client_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_ID') : ''); - $this->migrator->addEncrypted('discord.client_secret', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_SECRET') : ''); + $this->migrator->add('discord.bot_token', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:BOT_TOKEN') : ''); + $this->migrator->add('discord.client_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_ID') : ''); + $this->migrator->add('discord.client_secret', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_SECRET') : ''); $this->migrator->add('discord.guild_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:GUILD_ID') : ''); $this->migrator->add('discord.invite_url', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:INVITE_URL') : ''); $this->migrator->add('discord.role_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:ROLE_ID') : ''); diff --git a/public/install/forms.php b/public/install/forms.php index 9a772a57b..762222c6d 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -1,5 +1,6 @@ <?php + use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\PHPMailer; @@ -32,7 +33,7 @@ header('LOCATION: index.php?step=2&message=' . $e->getMessage()); exit(); } - + foreach ($values as $key => $value) { $param = $_POST[$value]; @@ -131,7 +132,7 @@ 'mail_host' => $_POST['host'], 'mail_port' => $_POST['port'], 'mail_username' => $_POST['user'], - 'mail_password' => encryptSettingsValue($_POST['pass']), + 'mail_password' => $_POST['pass'], 'mail_encryption' => $_POST['encryption'], 'mail_from_address' => $_POST['user'], ]; @@ -196,8 +197,8 @@ wh_log('Pterodactyl Settings are correct', 'debug'); wh_log('Updating Database', 'debug'); - $key = encryptSettingsValue($key); - $clientkey = encryptSettingsValue($clientkey); + $key = $key; + $clientkey = $clientkey; $query1 = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '" . json_encode($url) . "' WHERE (`name` = 'panel_url' AND `group` = 'pterodactyl')"; $query2 = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '" . json_encode($key) . "' WHERE (`name` = 'admin_token' AND `group` = 'pterodactyl')"; @@ -234,10 +235,10 @@ $repass = $_POST['repass']; $key = $db->query('SELECT `payload` FROM `' . getenv('DB_DATABASE') . "`.`settings` WHERE `name` = 'admin_token' AND `group` = 'pterodactyl'")->fetch_assoc(); - $key = encryptSettingsValue($key['value']); + $key = removeQuotes($key['payload']); $pterobaseurl = $db->query('SELECT `payload` FROM `' . getenv('DB_DATABASE') . "`.`settings` WHERE `name` = 'panel_url' AND `group` = 'pterodactyl'")->fetch_assoc(); - $pteroURL = $pterobaseurl['value'] . '/api/application/users/' . $pteroID; + $pteroURL = removeQuotes($pterobaseurl['payload']) . '/api/application/users/' . $pteroID; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $pteroURL); @@ -264,7 +265,7 @@ $name = $result['attributes']['username']; $pass = password_hash($pass, PASSWORD_DEFAULT); - $pteroURL = $pterobaseurl['value'] . '/api/application/users/' . $pteroID; + $pteroURL = removeQuotes($pterobaseurl['payload']) . '/api/application/users/' . $pteroID; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $pteroURL); @@ -272,7 +273,7 @@ curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', 'Content-Type: application/json', - 'Authorization: Bearer ' . $key['value'], + 'Authorization: Bearer ' . $key, ]); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'email' => $mail, @@ -300,4 +301,4 @@ wh_log($db->error, 'error'); header('LOCATION: index.php?step=6&message=Something went wrong when communicating with the Database'); } -} \ No newline at end of file +} diff --git a/public/install/functions.php b/public/install/functions.php index a7952046e..ebba003b1 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -150,6 +150,10 @@ function checkExtensions(): array return $not_ok; } +function removeQuotes($string){ + return str_replace('"', "", $string); +} + /** * Sets the environment variable into the env file * @param string $envKey The environment key to set or modify From 50d0e9a37d3371fa4685acfa8de31c83b11efef5 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 00:31:32 +0200 Subject: [PATCH 101/514] replace controlpanel.gg -> ctrlpanel.gg --- .env.example | 4 ++-- LICENSE | 2 +- README.md | 2 +- app/Extensions/PaymentGateways/PayPal/PayPalExtension.php | 2 +- app/Http/Controllers/HomeController.php | 2 +- config/app.php | 2 +- .../settings/2023_02_01_181453_create_mail_settings.php | 2 +- .../2023_02_01_182158_create_website_settings.php | 4 ++-- lang/de.json | 2 +- lang/en.json | 4 ++-- public/install/index.php | 6 +++--- themes/BlueInfinity/views/layouts/main.blade.php | 4 ++-- themes/default/views/admin/activitylogs/index.blade.php | 2 +- themes/default/views/admin/overview/index.blade.php | 8 ++++---- themes/default/views/auth/login.blade.php | 4 ++-- themes/default/views/layouts/main.blade.php | 4 ++-- 16 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.env.example b/.env.example index a025f65df..98f9662bd 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ ### --- App Settings --- ### -APP_NAME=Controlpanel.gg +APP_NAME=CtrlPanel.gg APP_ENV=production APP_KEY= APP_DEBUG=false @@ -64,4 +64,4 @@ MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" # Settings Cache -SETTINGS_CACHE_ENABLED=true \ No newline at end of file +SETTINGS_CACHE_ENABLED=true diff --git a/LICENSE b/LICENSE index 0ee0d65c7..00e2a0b66 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 ControlPanel.gg +Copyright (c) 2021 CtrlPanel.gg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2d2c062ad..f78d81a7f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ ControlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that credits users hourly for each server they have and suspends them if they run out of credits. -This dashboard offers an easy to use and free billing solution for all starting and experienced hosting providers. This dashboard has many customisation options and added discord Oauth verification to offer a solid link between your discord server and your dashboard. You can check our [Demo here](https://demo.controlpanel.gg "Demo"). +This dashboard offers an easy to use and free billing solution for all starting and experienced hosting providers. This dashboard has many customisation options and added discord Oauth verification to offer a solid link between your discord server and your dashboard. You can check our [Demo here](https://demo.CtrlPanel.gg "Demo"). ### [Installation](https://ctrlpanel.gg/docs/intro "Installation") diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index af4cc6059..6b1c335e8 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -87,7 +87,7 @@ static function PaypalPay(Request $request): void "application_context" => [ "cancel_url" => route('payment.Cancel'), "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id]), - 'brand_name' => config('app.name', 'Controlpanel.GG'), + 'brand_name' => config('app.name', 'CtrlPanel.GG'), 'shipping_preference' => 'NO_SHIPPING' ] diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 5162eebb4..11e597f1d 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -34,7 +34,7 @@ public function callHome() if (Storage::exists('callHome')) { return; } - Http::asForm()->post('https://market.controlpanel.gg/callhome.php', [ + Http::asForm()->post('https://market.CtrlPanel.gg/callhome.php', [ 'id' => Hash::make(URL::current()), ]); Storage::put('callHome', 'This is only used to count the installations of cpgg.'); diff --git a/config/app.php b/config/app.php index 045c3604a..534fafe3c 100644 --- a/config/app.php +++ b/config/app.php @@ -17,7 +17,7 @@ | */ - 'name' => env('APP_NAME', 'Controlpanel.gg'), + 'name' => env('APP_NAME', 'CtrlPanel.gg'), /* |-------------------------------------------------------------------------- diff --git a/database/settings/2023_02_01_181453_create_mail_settings.php b/database/settings/2023_02_01_181453_create_mail_settings.php index 7a1717320..cc609b44a 100644 --- a/database/settings/2023_02_01_181453_create_mail_settings.php +++ b/database/settings/2023_02_01_181453_create_mail_settings.php @@ -16,7 +16,7 @@ public function up(): void $this->migrator->add('mail.mail_password', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PASSWORD') : env('MAIL_PASSWORD', '')); $this->migrator->add('mail.mail_encryption', $table_exists ? $this->getOldValue('SETTINGS::MAIL:ENCRYPTION') : env('MAIL_ENCRYPTION', 'tls')); $this->migrator->add('mail.mail_from_address', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_ADDRESS') : env('MAIL_FROM_ADDRESS', 'example@example.com')); - $this->migrator->add('mail.mail_from_name', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_NAME') : env('APP_NAME', 'ControlPanel.gg')); + $this->migrator->add('mail.mail_from_name', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_NAME') : env('APP_NAME', 'CtrlPanel.gg')); $this->migrator->add('mail.mail_mailer', $table_exists ? $this->getOldValue('SETTINGS::MAIL:MAILER') : env('MAIL_MAILER', 'smtp')); $this->migrator->add('mail.mail_enabled', true); } diff --git a/database/settings/2023_02_01_182158_create_website_settings.php b/database/settings/2023_02_01_182158_create_website_settings.php index 176052db3..9aeb90364 100644 --- a/database/settings/2023_02_01_182158_create_website_settings.php +++ b/database/settings/2023_02_01_182158_create_website_settings.php @@ -14,7 +14,7 @@ public function up(): void $this->migrator->add( 'website.motd_message', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:MOTD_MESSAGE") : - '<h1 style="text-align: center;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://controlpanel.gg/img/controlpanel.png" alt="" width="200" height="200"><span style="font-size: 36pt;">Controlpanel.gg</span></h1> + '<h1 style="text-align: center;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ctrlpanel.gg/img/controlpanel.png" alt="" width="200" height="200"><span style="font-size: 36pt;">CtrlPanel.gg</span></h1> <p><span style="font-size: 18pt;">Thank you for using our Software</span></p> <p><span style="font-size: 18pt;">If you have any questions, make sure to join our <a href="https://discord.com/invite/4Y6HjD2uyU" target="_blank" rel="noopener">Discord</a></span></p> <p><span style="font-size: 10pt;">(you can change this message in the <a href="admin/settings#system">Settings</a> )</span></p>' @@ -23,7 +23,7 @@ public function up(): void $this->migrator->add('website.show_privacy', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_PRIVACY") : false); $this->migrator->add('website.show_tos', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_TOS") : false); $this->migrator->add('website.useful_links_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:USEFULLINKS_ENABLED") : true); - $this->migrator->add('website.seo_title', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SEO_TITLE") : 'ControlPanel.gg'); + $this->migrator->add('website.seo_title', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SEO_TITLE") : 'CtrlPanel.gg'); $this->migrator->add('website.seo_description', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SEO_DESCRIPTION") : 'Billing software for Pterodactyl Panel.'); $this->migrator->add('website.enable_login_logo', true); } diff --git a/lang/de.json b/lang/de.json index 9107634ba..638ede32d 100644 --- a/lang/de.json +++ b/lang/de.json @@ -126,7 +126,7 @@ "Support server": "Discord Server", "Documentation": "Dokumentation", "Github": "Github", - "Support ControlPanel": "Unterstütze Controlpanel.gg", + "Support ControlPanel": "Unterstütze CtrlPanel.gg", "Servers": "Server", "Total": "Gesamt", "Payments": "Zahlungen", diff --git a/lang/en.json b/lang/en.json index c0aa64314..af1126bdd 100644 --- a/lang/en.json +++ b/lang/en.json @@ -177,7 +177,7 @@ "Title": "Title", "User": "User", "Last updated": "Last updated", - "Controlpanel.gg": "Controlpanel.gg", + "CtrlPanel.gg": "CtrlPanel.gg", "Version": "Version", "Individual nodes": "Individual nodes", "You reached the Pterodactyl perPage limit. Please make sure to set it higher than your server count.": "You reached the Pterodactyl perPage limit. Please make sure to set it higher than your server count.", @@ -654,4 +654,4 @@ "sv": "Swedish", "sk": "Slovakish", "hu": "Hungarian" -} \ No newline at end of file +} diff --git a/public/install/index.php b/public/install/index.php index 54eb254d5..3a05e2b00 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -9,7 +9,7 @@ function cardStart($title, $subtitle = null) { return " <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> - <h1 class='text-center font-bold text-3xl'>ControlPanel.gg Installation</h1> + <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> <h2 class='text-xl text-center mb-2'>$title</h2>" . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); @@ -19,7 +19,7 @@ function cardStart($title, $subtitle = null) <html> <head> - <title>Controlpanel.gg installer Script</title> + <title>CtrlPanel.gg installer Script</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="/install/styles.css" rel="stylesheet"> @@ -68,7 +68,7 @@ function cardStart($title, $subtitle = null) // Getting started if (!isset($_GET['step']) || $_GET['step'] == 1) { ?> - <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of Controlpanel.gg's setup"); ?> + <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> <ul class="list-none mb-2"> diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index f5d888d5a..f965da56a 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -199,7 +199,7 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" alt="{{ config('app.name', 'Laravel') }} Logo" class="brand-image img-circle" style="opacity: .8"> - <span class="brand-text font-weight-light">{{ config('app.name', 'Controlpanel.gg') }}</span> + <span class="brand-text font-weight-light">{{ config('app.name', 'CtrlPanel.gg') }}</span> </a> <!-- Sidebar --> @@ -441,7 +441,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <strong>Copyright © 2021-{{ date('Y') }} <a href="{{ url('/') }}">{{ env('APP_NAME', 'Laravel') }}</a>.</strong> All rights - reserved. Powered by <a href="https://controlpanel.gg">ControlPanel</a>. | Theme by <a href="https://2icecube.de/cpgg">2IceCube</a> + reserved. Powered by <a href="https://CtrlPanel.gg">ControlPanel</a>. | Theme by <a href="https://2icecube.de/cpgg">2IceCube</a> @if (!str_contains(config('BRANCHNAME'), 'main') && !str_contains(config('BRANCHNAME'), 'unknown')) Version <b>{{ config('app')['version'] }} - {{ config('BRANCHNAME') }}</b> @endif diff --git a/themes/default/views/admin/activitylogs/index.blade.php b/themes/default/views/admin/activitylogs/index.blade.php index 822b06d68..fda21cf8e 100644 --- a/themes/default/views/admin/activitylogs/index.blade.php +++ b/themes/default/views/admin/activitylogs/index.blade.php @@ -33,7 +33,7 @@ @else <div class="callout callout-danger"> <h4>{{ __('No recent activity from cronjobs')}}</h4> - <p>{{ __('Are cronjobs running?')}} <a class="text-primary" target="_blank" href="https://controlpanel.gg/docs/Installation/getting-started#crontab-configuration">{{ __('Check the docs for it here')}}</a></p> + <p>{{ __('Are cronjobs running?')}} <a class="text-primary" target="_blank" href="https://CtrlPanel.gg/docs/Installation/getting-started#crontab-configuration">{{ __('Check the docs for it here')}}</a></p> </div> @endif diff --git a/themes/default/views/admin/overview/index.blade.php b/themes/default/views/admin/overview/index.blade.php index d801656e2..a092ed5f4 100644 --- a/themes/default/views/admin/overview/index.blade.php +++ b/themes/default/views/admin/overview/index.blade.php @@ -23,7 +23,7 @@ <b><i class="fas fa-shield-alt"></i> {{__("Version Outdated:")}}</b></br> {{__("You are running on")}} v{{config("app.version")}}-{{config("BRANCHNAME")}}. {{__("The latest Version is")}} v{{Storage::get('latestVersion')}}</br> - <a href="https://controlpanel.gg/docs/Installation/updating">{{__("Consider updating now")}}</a> + <a href="https://CtrlPanel.gg/docs/Installation/updating">{{__("Consider updating now")}}</a> </div> @endif </section> @@ -39,7 +39,7 @@ class="fab fa-discord mr-2"></i> {{__('Support server')}}</a> </div> <div class="col-md-3"> - <a href="https://controlpanel.gg/docs/intro" class="btn btn-dark btn-block px-3"><i + <a href="https://CtrlPanel.gg/docs/intro" class="btn btn-dark btn-block px-3"><i class="fas fa-link mr-2"></i> {{__('Documentation')}}</a> </div> <div class="col-md-3"> @@ -47,7 +47,7 @@ class="fas fa-link mr-2"></i> {{__('Documentation')}}</a> class="fab fa-github mr-2"></i> {{__('Github')}}</a> </div> <div class="col-md-3"> - <a href="https://controlpanel.gg/docs/Contributing/donating" class="btn btn-dark btn-block px-3"><i + <a href="https://CtrlPanel.gg/docs/Contributing/donating" class="btn btn-dark btn-block px-3"><i class="fas fa-money-bill mr-2"></i> {{__('Support ControlPanel')}}</a> </div> </div> @@ -199,7 +199,7 @@ class="fas fa-sync mr-2"></i>{{__('Sync')}}</a> <div class="card-header"> <div class="d-flex justify-content-between"> <div class="card-title "> - <span><i class="fas fa-server mr-2"></i>{{__('Controlpanel.gg')}}</span> + <span><i class="fas fa-server mr-2"></i>{{__('CtrlPanel.gg')}}</span> </div> </div> <div class="card-body py-1"> diff --git a/themes/default/views/auth/login.blade.php b/themes/default/views/auth/login.blade.php index 720effd5d..8c3cbb786 100644 --- a/themes/default/views/auth/login.blade.php +++ b/themes/default/views/auth/login.blade.php @@ -9,9 +9,9 @@ <div class="card-header text-center"> <a href="{{ route('welcome') }}" class="h1 mb-2"><b class="mr-1">{{ config('app.name', 'Laravel') }}</b></a> - @if ($website_settings->enable_login_logo) + @if ($website_settings->enable_login_logo) <img src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" - alt="{{ config('app.name', 'Controlpanel.gg') }} Logo" style="opacity: .8; max-width:100%; height: 150px; margin-top: 10px;"> + alt="{{ config('app.name', 'CtrlPanel.gg') }} Logo" style="opacity: .8; max-width:100%; height: 150px; margin-top: 10px;"> @endif </div> <div class="card-body pt-0"> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 30fb8eafc..0683b8757 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -204,7 +204,7 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" alt="{{ config('app.name', 'Laravel') }} Logo" class="brand-image img-circle" style="opacity: .8"> - <span class="brand-text font-weight-light">{{ config('app.name', 'Controlpanel.gg') }}</span> + <span class="brand-text font-weight-light">{{ config('app.name', 'CtrlPanel.gg') }}</span> </a> <!-- Sidebar --> @@ -443,7 +443,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <strong>Copyright © 2021-{{ date('Y') }} <a href="{{ url('/') }}">{{ env('APP_NAME', 'Laravel') }}</a>.</strong> All rights - reserved. Powered by <a href="https://controlpanel.gg">ControlPanel</a>. + reserved. Powered by <a href="https://CtrlPanel.gg">ControlPanel</a>. @if (!str_contains(config('BRANCHNAME'), 'main') && !str_contains(config('BRANCHNAME'), 'unknown')) Version <b>{{ config('app')['version'] }} - {{ config('BRANCHNAME') }}</b> @endif From bcf238d6b74fd97dff2b09e434662d26831747cd Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 00:38:47 +0200 Subject: [PATCH 102/514] rename controlpanel -> ctrlpanel --- .github/ISSUE_TEMPLATE/config.yml | 2 +- README.md | 8 ++++---- app/Console/Commands/GetGithubVersion.php | 2 +- config/mail.php | 2 +- themes/default/views/admin/overview/index.blade.php | 4 ++-- themes/default/views/admin/servers/edit.blade.php | 4 ++-- .../default/views/information/privacy-content.blade.php | 4 ++-- themes/default/views/layouts/main.blade.php | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 7b1652eaa..34e1506be 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -5,4 +5,4 @@ contact_links: about: Please visit our Discord for help with your installation. - name: ❓ General Question url: https://discord.gg/4Y6HjD2uyU - about: Please visit our Discord for general questions about the ControlPanel. + about: Please visit our Discord for general questions about the CtrlPanel. diff --git a/README.md b/README.md index f78d81a7f..aecde0b58 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,17 @@ - Theme Support - and so much more! -# ControlPanel-gg +# CtrlPanel-gg - +   -   [](https://crowdin.com/project/controlpanelgg)    +   [](https://crowdin.com/project/controlpanelgg)    ## About -ControlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that credits users hourly for each server they have and suspends them if they run out of credits. +CtrlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that credits users hourly for each server they have and suspends them if they run out of credits. This dashboard offers an easy to use and free billing solution for all starting and experienced hosting providers. This dashboard has many customisation options and added discord Oauth verification to offer a solid link between your discord server and your dashboard. You can check our [Demo here](https://demo.CtrlPanel.gg "Demo"). diff --git a/app/Console/Commands/GetGithubVersion.php b/app/Console/Commands/GetGithubVersion.php index 31e936252..99ee7e5b9 100644 --- a/app/Console/Commands/GetGithubVersion.php +++ b/app/Console/Commands/GetGithubVersion.php @@ -32,7 +32,7 @@ class GetGithubVersion extends Command public function handle() { try{ - $latestVersion = Http::get('https://api.github.com/repos/controlpanel-gg/dashboard/tags')->json()[0]['name']; + $latestVersion = Http::get('https://api.github.com/repos/ctrlpanel-gg/panel/tags')->json()[0]['name']; Storage::disk('local')->put('latestVersion', $latestVersion); } catch (Exception $e) { Storage::disk('local')->put('latestVersion', "unknown"); diff --git a/config/mail.php b/config/mail.php index 2974614ee..867155b50 100644 --- a/config/mail.php +++ b/config/mail.php @@ -93,7 +93,7 @@ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), - 'name' => env('MAIL_FROM_NAME', 'ControlPanel'), + 'name' => env('MAIL_FROM_NAME', 'CtrlPanel'), ], /* diff --git a/themes/default/views/admin/overview/index.blade.php b/themes/default/views/admin/overview/index.blade.php index a092ed5f4..d5b8d5a8e 100644 --- a/themes/default/views/admin/overview/index.blade.php +++ b/themes/default/views/admin/overview/index.blade.php @@ -48,7 +48,7 @@ class="fab fa-github mr-2"></i> {{__('Github')}}</a> </div> <div class="col-md-3"> <a href="https://CtrlPanel.gg/docs/Contributing/donating" class="btn btn-dark btn-block px-3"><i - class="fas fa-money-bill mr-2"></i> {{__('Support ControlPanel')}}</a> + class="fas fa-money-bill mr-2"></i> {{__('Support CtrlPanel')}}</a> </div> </div> @@ -227,7 +227,7 @@ class="fas fa-sync mr-2"></i>{{__('Sync')}}</a> <p class="mb-2"> {{ __('You reached the Pterodactyl perPage limit. Please make sure to set it higher than your server count.') }}<br> {{ __('You can do that in settings.') }}<br><br> - {{ __('Note') }}: {{ __('If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on ControlPanel. Try clicking the button below.') }} + {{ __('Note') }}: {{ __('If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on CtrlPanel. Try clicking the button below.') }} </p> <a href="{{route('admin.servers.sync')}}" class="btn btn-primary btn-md"><i class="fas fa-sync mr-2"></i>{{__('Sync servers')}}</a> diff --git a/themes/default/views/admin/servers/edit.blade.php b/themes/default/views/admin/servers/edit.blade.php index e3b9854f0..f9afd2b28 100644 --- a/themes/default/views/admin/servers/edit.blade.php +++ b/themes/default/views/admin/servers/edit.blade.php @@ -43,7 +43,7 @@ <div class="form-group"> <label for="identifier">{{ __('Server identifier') }} <i data-toggle="popover" data-trigger="hover" - data-content="{{ __('Change the server identifier on controlpanel to match a pterodactyl server.') }}" + data-content="{{ __('Change the server identifier on CtrlPanel to match a pterodactyl server.') }}" class="fas fa-info-circle"></i> </label> <input value="{{ $server->identifier }}" id="identifier" name="identifier" @@ -59,7 +59,7 @@ class="fas fa-info-circle"></i> <div class="form-group"> <label for="user_id">{{ __('Server owner') }} <i data-toggle="popover" data-trigger="hover" - data-content="{{ __('Change the current server owner on controlpanel and pterodactyl.') }}" + data-content="{{ __('Change the current server owner on CtrlPanel and pterodactyl.') }}" class="fas fa-info-circle"></i> </label> <select name="user_id" id="user_id" class="form-control"> diff --git a/themes/default/views/information/privacy-content.blade.php b/themes/default/views/information/privacy-content.blade.php index af4144858..5f465e829 100644 --- a/themes/default/views/information/privacy-content.blade.php +++ b/themes/default/views/information/privacy-content.blade.php @@ -18,7 +18,7 @@ </li> <li> <p><strong>Company</strong> (referred to as either "the Company", "We", "Us" or - "Our" in this Agreement) refers to controlpanel.</p> + "Our" in this Agreement) refers to CtrlPanel.</p> </li> <li> <p><strong>Cookies</strong> are small files that are placed on Your computer, mobile device or any other device @@ -49,7 +49,7 @@ Service or from the Service infrastructure itself (for example, the duration of a page visit).</p> </li> <li> - <p><strong>Website</strong> refers to controlpanel, accessible from <a href="controlpanel" + <p><strong>Website</strong> refers to CtrlPanel, accessible from <a href="controlpanel" rel="external nofollow noopener" target="_blank">controlpanel</a></p> </li> <li> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 0683b8757..6e1f6f01e 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -443,7 +443,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <strong>Copyright © 2021-{{ date('Y') }} <a href="{{ url('/') }}">{{ env('APP_NAME', 'Laravel') }}</a>.</strong> All rights - reserved. Powered by <a href="https://CtrlPanel.gg">ControlPanel</a>. + reserved. Powered by <a href="https://CtrlPanel.gg">CtrlPanel</a>. @if (!str_contains(config('BRANCHNAME'), 'main') && !str_contains(config('BRANCHNAME'), 'unknown')) Version <b>{{ config('app')['version'] }} - {{ config('BRANCHNAME') }}</b> @endif From 797d871b6304afda198e04fb8f93eac9198e1c67 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 00:39:57 +0200 Subject: [PATCH 103/514] fix double useful links? --- themes/default/views/home.blade.php | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/themes/default/views/home.blade.php b/themes/default/views/home.blade.php index 29d129266..8f106c949 100644 --- a/themes/default/views/home.blade.php +++ b/themes/default/views/home.blade.php @@ -135,12 +135,12 @@ class="info-box-text">{{ __('Out of Credits in', ['credits' => $general_settings </div> <!-- /.card-header --> <div class="card-body"> - @foreach ($useful_links as $useful_link) + @foreach ($useful_links_dashboard as $useful_link) <div class="alert alert-dismissible"> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> <h5> <a class="alert-link text-decoration-none" target="__blank" - href="{{ $useful_link->link }}"> + href="{{ $useful_link->link }}"> <i class="{{ $useful_link->icon }} mr-2"></i>{{ $useful_link->title }} </a> </h5> @@ -150,22 +150,7 @@ class="info-box-text">{{ __('Out of Credits in', ['credits' => $general_settings </div> <!-- /.card-body --> </div> - <!-- /.card-header --> - <div class="card-body"> - @foreach ($useful_links_dashboard as $useful_link) - <div class="alert alert-dismissible"> - <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> - <h5> - <a class="alert-link text-decoration-none" target="__blank" - href="{{ $useful_link->link }}"> - <i class="{{ $useful_link->icon }} mr-2"></i>{{ $useful_link->title }} - </a> - </h5> - {!! $useful_link->description !!} - </div> - @endforeach - </div> - <!-- /.card-body --> + </div> @endif From fe2020f81760d5a2d0b4f4940151099948f0a413 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 00:47:15 +0200 Subject: [PATCH 104/514] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aecde0b58..4e6591b4d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@   -   [](https://crowdin.com/project/controlpanelgg)    +   [](https://crowdin.com/project/controlpanelgg)    ## About CtrlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that credits users hourly for each server they have and suspends them if they run out of credits. From bfda91516af2357ab98a641fc0b53be608487f45 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 01:00:45 +0200 Subject: [PATCH 105/514] add recaptcha to tickets and preview to settings --- app/Http/Controllers/TicketsController.php | 1 + .../views/admin/settings/index.blade.php | 22 +++++++++++++++++++ themes/default/views/ticket/create.blade.php | 11 ++++++++++ 3 files changed, 34 insertions(+) diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index 8cd988149..754241edd 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -39,6 +39,7 @@ public function store(Request $request, TicketSettings $ticket_settings) 'ticketcategory' => 'required', 'priority' => 'required', 'message' => 'required', + 'g-recaptcha-response' => ['required', 'recaptcha'], ] ); $ticket = new Ticket( diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 8b0c36126..89249eac9 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -159,6 +159,28 @@ class="custom-select w-100" name="{{ $key }}" </div> </div> @endforeach + <div class="row"> + <div class="col-4 d-flex align-items-center"> + <label for="recaptcha_preview">{{__("ReCAPTCHA Preview")}}</label> + </div> + + <div class="col-8"> + + <div class="w-100"> + <div class="input-group mb-3"> + {!! htmlScriptTagJsApi() !!} + {!! htmlFormSnippet() !!} + @error('g-recaptcha-response') + <span class="text-danger" role="alert"> + <small><strong>{{ $message }}</strong></small> + </span> + @enderror + </div> + </div> + </div> + </div> + + <div class="row"> <div class="col-12 d-flex align-items-center justify-content-end"> <button type="submit" diff --git a/themes/default/views/ticket/create.blade.php b/themes/default/views/ticket/create.blade.php index 7da409cfa..f9d35ad4d 100644 --- a/themes/default/views/ticket/create.blade.php +++ b/themes/default/views/ticket/create.blade.php @@ -90,6 +90,17 @@ </span> @endif </div> + @if (app(App\Settings\GeneralSettings::class)->recaptcha_enabled) + <div class="input-group mb-3"> + {!! htmlScriptTagJsApi() !!} + {!! htmlFormSnippet() !!} + @error('g-recaptcha-response') + <span class="text-danger" role="alert"> + <small><strong>{{ $message }}</strong></small> + </span> + @enderror + </div> + @endif </div> <div class="card-footer"> <button type="submit" class="btn btn-primary ticket-once"> From 02798b2c30981e16625e23ec32c5e9c9cab1ceaf Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 01:12:49 +0200 Subject: [PATCH 106/514] installer fix --- .../2023_02_01_182158_create_website_settings.php | 8 ++++---- public/install/index.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/database/settings/2023_02_01_182158_create_website_settings.php b/database/settings/2023_02_01_182158_create_website_settings.php index 9aeb90364..013c85f87 100644 --- a/database/settings/2023_02_01_182158_create_website_settings.php +++ b/database/settings/2023_02_01_182158_create_website_settings.php @@ -14,10 +14,10 @@ public function up(): void $this->migrator->add( 'website.motd_message', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:MOTD_MESSAGE") : - '<h1 style="text-align: center;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ctrlpanel.gg/img/controlpanel.png" alt="" width="200" height="200"><span style="font-size: 36pt;">CtrlPanel.gg</span></h1> - <p><span style="font-size: 18pt;">Thank you for using our Software</span></p> - <p><span style="font-size: 18pt;">If you have any questions, make sure to join our <a href="https://discord.com/invite/4Y6HjD2uyU" target="_blank" rel="noopener">Discord</a></span></p> - <p><span style="font-size: 10pt;">(you can change this message in the <a href="admin/settings#system">Settings</a> )</span></p>' + '<h1 style="text-align: center;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ctrlpanel.gg/img/controlpanel.png" alt="" width="200" height="200"><span style="font-size: 36pt;">Controlpanel.gg</span></h1> + <p><span style="font-size: 18pt;">Thank you for using our Software</span></p> + <p><span style="font-size: 18pt;">If you have any questions, make sure to join our <a href="https://discord.com/invite/4Y6HjD2uyU" target="_blank" rel="noopener">Discord</a></span></p> + <p><span style="font-size: 10pt;">(you can change this message in the <a href="admin/settings#system">Settings</a> )</span></p>' ); $this->migrator->add('website.show_imprint', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_IMPRINT") : false); $this->migrator->add('website.show_privacy', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_PRIVACY") : false); diff --git a/public/install/index.php b/public/install/index.php index 3a05e2b00..6afd2e70c 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -327,7 +327,7 @@ function cardStart($title, $subtitle = null) <div class="flex flex-col mb-3"> <label for="url">Pterodactyl URL</label> - <input id="url" name="url" type="text" required value="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> <div class="form-group"> From 0ffceb535d0385dccdf15b94a3697c2441c24329 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 02:22:59 +0200 Subject: [PATCH 107/514] simple starting permissions. only admin --- app/Http/Controllers/Admin/RoleController.php | 190 ++++++++++++++++++ app/Http/Controllers/Admin/UserController.php | 2 +- app/Http/Controllers/Controller.php | 32 +++ app/Http/Controllers/ProfileController.php | 2 +- app/Http/Kernel.php | 5 + app/Http/Middleware/isAdmin.php | 2 +- app/Http/Middleware/isMod.php | 2 +- app/Models/User.php | 3 +- composer.json | 1 + composer.lock | 84 +++++++- config/permission.php | 161 +++++++++++++++ config/permissions_web.php | 92 +++++++++ ..._04_29_232942_create_permission_tables.php | 142 +++++++++++++ .../2023_04_29_233120_drop_roles.php | 51 +++++ database/seeders/PermissionsSeeder.php | 74 +++++++ public/install/forms.php | 6 +- routes/web.php | 5 +- .../BlueInfinity/views/layouts/main.blade.php | 4 +- .../default/views/admin/roles/edit.blade.php | 54 +++++ .../default/views/admin/roles/index.blade.php | 58 ++++++ .../default/views/admin/users/edit.blade.php | 22 +- .../default/views/admin/users/show.blade.php | 2 +- themes/default/views/home.blade.php | 2 +- themes/default/views/layouts/main.blade.php | 11 +- .../views/moderator/ticket/show.blade.php | 4 +- themes/default/views/servers/create.blade.php | 2 +- themes/default/views/ticket/show.blade.php | 4 +- 27 files changed, 980 insertions(+), 37 deletions(-) create mode 100644 app/Http/Controllers/Admin/RoleController.php create mode 100644 config/permission.php create mode 100644 config/permissions_web.php create mode 100644 database/migrations/2023_04_29_232942_create_permission_tables.php create mode 100644 database/migrations/2023_04_29_233120_drop_roles.php create mode 100644 database/seeders/PermissionsSeeder.php create mode 100644 themes/default/views/admin/roles/edit.blade.php create mode 100644 themes/default/views/admin/roles/index.blade.php diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php new file mode 100644 index 000000000..22a8c113f --- /dev/null +++ b/app/Http/Controllers/Admin/RoleController.php @@ -0,0 +1,190 @@ +<?php + +namespace App\Http\Controllers\Admin; + +use App\Http\Controllers\Controller; +use Exception; +use Illuminate\Contracts\Foundation\Application; +use Illuminate\Contracts\View\Factory; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Spatie\Permission\Models\Permission; +use Spatie\Permission\Models\Role; + +class RoleController extends Controller +{ + + /** + * Display a listing of the resource. + * + * @param Request $request + * @return mixed + * @throws Exception + */ + public function index(Request $request) + { + + + //datatables + if ($request->ajax()) { + return $this->dataTableQuery(); + } + + $html = $this->dataTable(); + return view('admin.roles.index', compact('html')); + } + + /** + * Show the form for creating a new resource. + * + * @return Application|Factory|View + */ + public function create() + { + + $permissions = Permission::all(); + + return view('admin.roles.edit', compact('permissions')); + } + + /** + * Store a newly created resource in storage. + * + * @return RedirectResponse + */ + public function store(Request $request): RedirectResponse + { + $role = Role::create([ + 'name' => $request->name, + 'color' => $request->color + ]); + + if ($request->permissions) { + $role->givePermissionTo($request->permissions); + } + + return redirect() + ->route('admin.roles.index') + ->with('success', __('Role saved')); + } + + /** + * Display the specified resource. + */ + public function show() + { + abort(404); + } + + /** + * Show the form for editing the specified resource. + * + * @param Role $role + * @return Application|Factory|View + */ + public function edit(Role $role) + { + + $permissions = Permission::all(); + + return view('admin.roles.edit', compact('role', 'permissions')); + } + + /** + * Update the specified resource in storage. + * + * @param Role $role + * @return RedirectResponse + */ + public function update(Request $request, Role $role) + { + if ($request->permissions) { + if($role->id != 1){ //disable admin permissions change + $role->syncPermissions($request->permissions); + } + } + + if($role->id == 3 || $role->id == 1 || $role->id == 4){ //dont let the user change the names of these roles + $role->update([ + 'color' => $request->color + ]); + }else{ + $role->update([ + 'name' => $request->name, + 'color' => $request->color + ]); + } + + if($role->id == 1){ + return redirect()->route('admin.roles.index')->with('success', __('Role updated. Name and Permissions of this Role cannot be changed')); + }elseif($role->id == 4 || $role->id == 3){ + return redirect()->route('admin.roles.index')->with('success', __('Role updated. Name of this Role cannot be changed')); + }else{ + return redirect() + ->route('admin.roles.index') + ->with('success', __('Role saved')); + } + } + + /** + * Remove the specified resource from storage. + * + * @return RedirectResponse + */ + public function destroy(Role $role) + { + + if($role->id == 3 || $role->id == 1 || $role->id == 2){ //cannot delete the hard coded roles + return back()->with("error","You cannot delete that role"); + } + + $users = User::role($role)->get(); + + foreach($users as $user){ + $user->syncRoles(['Member']); + } + + $role->delete(); + + return redirect() + ->route('admin.roles.index') + ->with('success', __('Role removed')); + } + + /** + * @return mixed + * @throws Exception + */ + public function dataTable() + { + $query = Role::query()->withCount(['users', 'permissions']); + + + return datatables($query) + ->addColumn('actions', function (Role $role) { + return ' + <a title="Edit" href="'.route("admin.roles.edit", $role).'" class="btn btn-sm btn-info"><i + class="fa fas fa-edit"></i></a> + <form class="d-inline" method="post" action="'.route("admin.roles.destroy", $role).'"> + ' . csrf_field() . ' + ' . method_field("DELETE") . ' + <button title="Delete" type="submit" class="btn btn-sm btn-danger confirm"><i + class="fa fas fa-trash"></i></button> + </form> + '; + }) + + ->editColumn('name', function (Role $role) { + return "<span style=\"color: $role->color\">$role->name</span>"; + }) + ->editColumn('usercount', function ($query) { + return $query->users_count; + }) + ->editColumn('permissionscount', function ($query){ + return $query->permissions_count; + }) + ->rawColumns(['actions', 'name']) + ->make(true); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 4dfcb219f..a7e494a9c 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -166,7 +166,7 @@ public function update(Request $request, User $user) */ public function destroy(User $user) { - if ($user->role === 'admin' && User::query()->where('role', 'admin')->count() === 1) { + if ($user->hasRole("Admin") && User::query()->where('role', 'admin')->count() === 1) { return redirect()->back()->with('error', __('You can not delete the last admin!')); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index a0a2a8a34..49d020497 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,12 +2,44 @@ namespace App\Http\Controllers; +use App\Models\User; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller as BaseController; +use Illuminate\Support\Facades\Auth; class Controller extends BaseController { use AuthorizesRequests, DispatchesJobs, ValidatesRequests; + /** + * Check if user has permissions + * Abort 403 if the user doesn't have the required permission + * + * @param string $permission + * @return void + */ + public function checkPermission(string $permission) + { + /** @var User $user */ + $user = Auth::user(); + + if (!$user->can($permission)) { + abort(403, __('User does not have the right permissions.')); + } + } + + /** + * Check if user has permissions + * + * @param string $permission + * @return bool + */ + public function can(string $permission): bool + { + /** @var User $user */ + $user = Auth::user(); + + return $user->can($permission); + } } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index b3156f96e..5c4293aed 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -57,7 +57,7 @@ public function index(UserSettings $user_settings, DiscordSettings $discord_sett public function selfDestroyUser() { $user = Auth::user(); - if ($user->role == "admin") return back()->with("error", "You cannot delete yourself as an admin!"); + if ($user->hasRole("Admin")) return back()->with("error", "You cannot delete yourself as an admin!"); $user->delete(); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3e372e091..a6fb149b0 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -27,6 +27,7 @@ class Kernel extends HttpKernel \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + ]; /** @@ -76,5 +77,9 @@ class Kernel extends HttpKernel 'moderator' => isMod::class, 'api.token' => ApiAuthToken::class, 'checkSuspended' => CheckSuspended::class, + 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, + 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, + 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, ]; + } diff --git a/app/Http/Middleware/isAdmin.php b/app/Http/Middleware/isAdmin.php index 3dbb49574..1bf4f55af 100644 --- a/app/Http/Middleware/isAdmin.php +++ b/app/Http/Middleware/isAdmin.php @@ -18,7 +18,7 @@ class isAdmin */ public function handle(Request $request, Closure $next) { - if (Auth::user() && Auth::user()->role == 'admin') { + if (Auth::user() && Auth::user()->hasRole("Admin")) { return $next($request); } diff --git a/app/Http/Middleware/isMod.php b/app/Http/Middleware/isMod.php index c9120719c..8c5453a24 100644 --- a/app/Http/Middleware/isMod.php +++ b/app/Http/Middleware/isMod.php @@ -18,7 +18,7 @@ class isMod */ public function handle(Request $request, Closure $next) { - if (Auth::user() && Auth::user()->role == 'moderator' || Auth::user() && Auth::user()->role == 'admin') { + if (Auth::user() && Auth::user()->role == 'moderator' || Auth::user() && Auth::user()->hasRole("Admin")) { return $next($request); } diff --git a/app/Models/User.php b/app/Models/User.php index 9584bd3db..d25a71009 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -18,13 +18,14 @@ use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\CausesActivity; use Spatie\Activitylog\Traits\LogsActivity; +use Spatie\Permission\Traits\HasRoles; /** * Class User */ class User extends Authenticatable implements MustVerifyEmail { - use HasFactory, Notifiable, LogsActivity, CausesActivity; + use HasFactory, Notifiable, LogsActivity, CausesActivity, HasRoles; private PterodactylClient $pterodactyl; diff --git a/composer.json b/composer.json index 7a4a1e643..b316eec10 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "qirolab/laravel-themer": "^2.0.2", "socialiteproviders/discord": "^4.1.2", "spatie/laravel-activitylog": "^4.7.3", + "spatie/laravel-permission": "^5.10", "spatie/laravel-query-builder": "^5.1.2", "spatie/laravel-settings": "^2.7", "spatie/laravel-validation-rules": "^3.2.2", diff --git a/composer.lock b/composer.lock index 96825a92c..3f224c0c4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0d007fe2e018692a9ff3d50fcbebabc5", + "content-hash": "8a9b4a3cda2a919fa33f41527b679dce", "packages": [ { "name": "aws/aws-crt-php", @@ -5160,6 +5160,88 @@ ], "time": "2023-04-27T08:09:01+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "5.10.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "d08b3ffc5870cce4a47a39f22174947b33c191ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/d08b3ffc5870cce4a47a39f22174947b33c191ae", + "reference": "d08b3ffc5870cce4a47a39f22174947b33c191ae", + "shasum": "" + }, + "require": { + "illuminate/auth": "^7.0|^8.0|^9.0|^10.0", + "illuminate/container": "^7.0|^8.0|^9.0|^10.0", + "illuminate/contracts": "^7.0|^8.0|^9.0|^10.0", + "illuminate/database": "^7.0|^8.0|^9.0|^10.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^5.0|^6.0|^7.0|^8.0", + "phpunit/phpunit": "^9.4", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.x-dev", + "dev-master": "5.x-dev" + }, + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 6.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/5.10.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2023-04-12T17:08:32+00:00" + }, { "name": "spatie/laravel-query-builder", "version": "5.2.0", diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 000000000..5b6e184c3 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,161 @@ +<?php + +return [ + + 'models' => [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false, if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true the package implements teams using the 'team_foreign_key'. If you want + * the migrations to register the 'team_foreign_key', you must set this to true + * before doing the migration. If you already did the migration then you must make a new + * migration to also add 'team_foreign_key' to 'roles', 'model_has_roles', and + * 'model_has_permissions'(view the latest version of package's migration file) + */ + + 'teams' => false, + + /* + * When set to true, the required permission names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + */ + + 'enable_wildcard_permission' => false, + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/config/permissions_web.php b/config/permissions_web.php new file mode 100644 index 000000000..accbd07a0 --- /dev/null +++ b/config/permissions_web.php @@ -0,0 +1,92 @@ +<?php + +return [ + '*', + + /* + * Permissions for admin + */ + 'admin.sidebar.read', + + 'admin.roles.read', + 'admin.roles.write', + + + 'admin.ticket.read', + + 'admin.ticket_blacklist.read', + 'admin.ticket_blacklist.write', + + 'admin.overview.read', + 'admin.overview.sync', + + 'admin.api.read', + 'admin.api.write', + + 'admin.users.read', + 'admin.users.write', + 'admin.users.suspend', + 'admin.users.write.credits', + 'admin.users.write.username', + 'admin.users.write.password', + 'admin.users.write.role', + 'admin.users.write.referal', + 'admin.users.write.pterodactyl', + + 'admin.servers.read', + 'admin.servers.write', + 'admin.servers.suspend', + 'admin.server.write.owner', + 'admin.server.write.identifier', + 'admin.server.delete', + + 'admin.products.read', + 'admin.products.create', + 'admin.products.edit', + 'admin.products.delete', + + 'admin.store.read', + 'admin.store.write', + 'admin.store.disable', + + 'admin.voucher.read', + 'admin.voucher.write', + + 'admin.useful_links.read', + 'admin.useful_links.write', + + 'admin.legal.read', + 'admin.legal.write', + + 'admin.logs.read', + + /* + * Permissions for settings + */ + 'settings.sidebar.read', + + 'settings.invoices.read', + 'settings.invoices.write', + + 'settings.language.read', + 'settings.language.write', + + 'settings.misc.read', + 'settings.misc.write', + + 'settings.payment.read', + 'settings.payment.write', + + 'settings.system.read', + 'settings.system.write', + + /* + * Permissions for users + */ + 'user.server.create', + 'user.server.upgrade', + 'user.shop.buy', + 'user.ticket.read', + 'user.ticket.write', + 'user.referral', +]; diff --git a/database/migrations/2023_04_29_232942_create_permission_tables.php b/database/migrations/2023_04_29_232942_create_permission_tables.php new file mode 100644 index 000000000..01f845157 --- /dev/null +++ b/database/migrations/2023_04_29_232942_create_permission_tables.php @@ -0,0 +1,142 @@ +<?php + +use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Migrations\Migration; +use Spatie\Permission\PermissionRegistrar; + +class CreatePermissionTables extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + $tableNames = config('permission.table_names'); + $columnNames = config('permission.column_names'); + $teams = config('permission.teams'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); + } + if ($teams && empty($columnNames['team_foreign_key'] ?? null)) { + throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'); + } + + Schema::create($tableNames['permissions'], function (Blueprint $table) { + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('color')->nullable()->default('#485460'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign(PermissionRegistrar::$pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign(PermissionRegistrar::$pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + + $table->foreign(PermissionRegistrar::$pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign(PermissionRegistrar::$pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +} diff --git a/database/migrations/2023_04_29_233120_drop_roles.php b/database/migrations/2023_04_29_233120_drop_roles.php new file mode 100644 index 000000000..3f3657077 --- /dev/null +++ b/database/migrations/2023_04_29_233120_drop_roles.php @@ -0,0 +1,51 @@ +<?php + +use App\Models\User; +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Artisan::call('db:seed', [ + '--class' => 'PermissionsSeeder', + ]); + + Schema::table('users', function ($table) { + $table->dropColumn('role'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function($table) { + $table->string('role')->default('member'); + }); + + $users = User::with('roles')->get(); + foreach($users as $user){ + if($user->hasRole(1)){ + $user->role = "admin"; + }elseif ($user->hasRole(3)){ + $user->role = "client"; + }else{ + $user->role = "member"; + } + $user->save(); + } + + } +}; diff --git a/database/seeders/PermissionsSeeder.php b/database/seeders/PermissionsSeeder.php new file mode 100644 index 000000000..7d6ebc062 --- /dev/null +++ b/database/seeders/PermissionsSeeder.php @@ -0,0 +1,74 @@ +<?php + +namespace Database\Seeders; + +use App\Models\User; +use Illuminate\Database\Console\Seeds\WithoutModelEvents; +use Illuminate\Database\Seeder; +use Spatie\Permission\Models\Permission; +use Spatie\Permission\Models\Role; + +class PermissionsSeeder extends Seeder +{ + /** + * Run the database seeds. + * + * @return void + */ + public function run() + { + + $this->createPermissions(); + $this->createRoles(); + + + $users = User::all(); + foreach($users as $user){ + $user->assignRole(4); + } + + $admins = User::where("role","admin")->get(); + foreach($admins as $admin) { + $admin->syncRoles(1); + } + + $admins = User::where("role","client")->get(); + foreach($admins as $admin) { + $admin->syncRoles(3); + } + + + + + } + + public function createPermissions() + { + foreach (config('permissions_web') as $name) { + Permission::findOrCreate($name); + } + } + + //TODO run only once + public function createRoles() + { + $userPermissions=[ + 'user.server.create', + 'user.server.upgrade', + 'user.shop.buy', + 'user.ticket.read', + 'user.ticket.write', + 'user.referral', + ]; + /** @var Role $adminRole */ + $adminRole = Role::updateOrCreate(["name"=>"Admin","color"=>"#fa0000"]); + $supportRole = Role::updateOrCreate(["name"=>"Support-Team","color"=>"#00b0b3"]); + $clientRole = Role::updateOrCreate(["name"=>"Client","color"=>"#008009"]); + $userRole = Role::updateOrCreate(["name"=>"User","color"=>"#0052a3"]); + + $adminRole->givePermissionTo(Permission::findByName('*')); + + $userRole->syncPermissions($userPermissions); + $clientRole->syncPermissions($userPermissions); + } +} diff --git a/public/install/forms.php b/public/install/forms.php index 762222c6d..d6d72b92c 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -292,9 +292,9 @@ } $random = generateRandomString(); - $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; - - if ($db->query($query1)) { + $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; + $query2 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\Models\User', '1')"; + if ($db->query($query1) && $db->query($query2)) { wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID, 'info'); header('LOCATION: index.php?step=7'); } else { diff --git a/routes/web.php b/routes/web.php index 438b4dd5c..f13a33a99 100644 --- a/routes/web.php +++ b/routes/web.php @@ -13,6 +13,7 @@ use App\Http\Controllers\Admin\PartnerController; use App\Http\Controllers\Admin\PaymentController; use App\Http\Controllers\Admin\ProductController; +use App\Http\Controllers\Admin\RoleController; use App\Http\Controllers\Admin\ServerController as AdminServerController; use App\Http\Controllers\Admin\SettingsController; use App\Http\Controllers\Admin\ShopProductController; @@ -117,7 +118,9 @@ //admin Route::prefix('admin')->name('admin.')->middleware('admin')->group(function () { - + //Roles + Route::get('roles/datatable', [RoleController::class, 'datatable'])->name('roles.datatable'); + Route::resource('roles', RoleController::class); //overview Route::get('legal', [OverViewController::class, 'index'])->name('overview.index'); diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index f965da56a..a5c13de11 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -253,7 +253,7 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> </li> @endif - @if ((Auth::user()->role == 'admin' || Auth::user()->role == 'moderator') && config('SETTINGS::TICKET:ENABLED')) + @if ((Auth::user()->hasRole("Admin") || Auth::user()->role == 'moderator') && config('SETTINGS::TICKET:ENABLED')) <li class="nav-header">{{ __('Moderation') }}</li> <li class="nav-item"> @@ -272,7 +272,7 @@ class="nav-link @if (Request::routeIs('moderator.ticket.blacklist')) active @end </li> @endif - @if (Auth::user()->role == 'admin') + @if (Auth::user()->hasRole("Admin")) <li class="nav-header">{{ __('Administration') }}</li> <li class="nav-item"> diff --git a/themes/default/views/admin/roles/edit.blade.php b/themes/default/views/admin/roles/edit.blade.php new file mode 100644 index 000000000..2abbe8087 --- /dev/null +++ b/themes/default/views/admin/roles/edit.blade.php @@ -0,0 +1,54 @@ +@extends('layouts.main') + +@section('content') + <div class="main py-4"> + + <div class="card card-body border-0 shadow table-wrapper table-responsive"> + <h2 class="mb-4 h5">{{ isset($role) ? __('Edit role') : __('Create role') }}</h2> + + <form method="post" + action="{{isset($role) ? route('admin.roles.update', $role->id) : route('admin.roles.store')}}"> + @csrf + @isset($role) + @method('PATCH') + @endisset + + <div class="row"> + <div class="col-lg-6"> + + <x-input.text label="{{(__('Name'))}}" + name="name" + value="{{ isset($role) ? $role->name : null}}"/> + + <x-input.text label="{{(__('Badge color'))}}" + type="color" + name="color" + value="{{ isset($role) ? $role->color : null}}"/> + + </div> + + <div class="col-lg-6"> + + <x-input.select + label="{{(__('Permissions'))}}" + name="permissions" + style="height: 200px" + multiple> + @foreach($permissions as $permission) + <option @if(isset($role) && $role->permissions->contains($permission)) selected + @endif value="{{$permission->id}}">{{$permission->name}}</option> + @endforeach + </x-input.select> + + </div> + </div> + + <div class="form-group d-flex justify-content-end mt-3"> + <button name="submit" type="submit" class="btn btn-primary">{{__('Submit')}}</button> + </div> + </form> + + </div> + + </div> +@endsection diff --git a/themes/default/views/admin/roles/index.blade.php b/themes/default/views/admin/roles/index.blade.php new file mode 100644 index 000000000..71c88d92f --- /dev/null +++ b/themes/default/views/admin/roles/index.blade.php @@ -0,0 +1,58 @@ +@extends('layouts.main') + +@section('content') + <div class="main py-4"> + + @can('admin.roles.write') + <div class="d-flex justify-content-end my-3"> + <a href="{{route('admin.roles.create')}}" class="btn btn-primary"><i + class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> + </div> + @endcan + + <div class="card card-body border-0 shadow table-wrapper table-responsive"> + <h2 class="mb-4 h5">{{ __('Roles') }}</h2> + + <div class="card-body table-responsive"> + + <table id="datatable" class="table table-striped"> + <thead> + <tr> + <th>{{__("Name")}}</th> + <th>{{__("User count")}}</th> + <th>{{__("Permissions count")}}</th> + <th>{{__("Actions")}}</th> + </tr> + </thead> + <tbody> + </tbody> + </table> + + </div> + </div> + </div> +@endsection +<script> + + document.addEventListener("DOMContentLoaded", function () { + $('#datatable').DataTable({ + language: { + url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("SETTINGS::LOCALE:DATATABLES")}}.json' + }, + processing: true, + serverSide: false, //increases loading times too much? change back to "true" if it does + stateSave: true, + ajax: "{{route('admin.roles.datatable')}}", + columns: [ + {data: 'name'}, + {data: 'usercount'}, + {data: 'permissionscount'}, + {data: 'actions' , sortable : false}, + ], + fnDrawCallback: function( oSettings ) { + $('[data-toggle="popover"]').popover(); + } + }); + }); +</script> + diff --git a/themes/default/views/admin/users/edit.blade.php b/themes/default/views/admin/users/edit.blade.php index 94c157857..b4c840bfc 100644 --- a/themes/default/views/admin/users/edit.blade.php +++ b/themes/default/views/admin/users/edit.blade.php @@ -97,24 +97,14 @@ class="form-control @error('server_limit') is-invalid @enderror" <div class="form-group"> <label for="role">{{__('Role')}}</label> <div> - <select id="role" name="role" + <select id="roles" name="roles" class="custom-select @error('role') is-invalid @enderror" required="required"> - <option @if($user->role == 'admin') selected @endif class="text-danger" - value="admin"> - {{__(' Administrator')}} - </option> - <option @if($user->role == 'moderator') selected @endif class="text-info" value="moderator"> - {{__('Moderator')}} - </option> - <option @if($user->role == 'client') selected @endif class="text-success" - value="client"> - {{__('Client')}} - </option> - <option @if($user->role == 'member') selected @endif class="text-secondary" - value="member"> - {{__('Member')}} - </option> + @foreach($roles as $role) + <option style="color: {{$role->color}}" + @if(isset($user) && $user->roles->contains($role)) selected + @endif value="{{$role->id}}">{{$role->name}}</option> + @endforeach </select> </div> </div> diff --git a/themes/default/views/admin/users/show.blade.php b/themes/default/views/admin/users/show.blade.php index e02baef32..5d2da4120 100644 --- a/themes/default/views/admin/users/show.blade.php +++ b/themes/default/views/admin/users/show.blade.php @@ -76,7 +76,7 @@ <div class="col-lg-8"> <span style="max-width: 250px;" class="d-inline-block text-truncate badge - @if ($user->role == 'admin') badge-danger + @if ($user->hasRole("Admin")) badge-danger @elseif ($user->role == 'moderator') badge-info @elseif ($user->role == 'client') diff --git a/themes/default/views/home.blade.php b/themes/default/views/home.blade.php index 8f106c949..119c50ffe 100644 --- a/themes/default/views/home.blade.php +++ b/themes/default/views/home.blade.php @@ -18,7 +18,7 @@ </section> <!-- END CONTENT HEADER --> - @if (!file_exists(base_path() . '/install.lock') && Auth::User()->role == 'admin') + @if (!file_exists(base_path() . '/install.lock') && Auth::User()->hasRole("Admin")) <div class="callout callout-danger"> <h4>{{ __('The installer is not locked!') }}</h4> <p>{{ __('please create a file called "install.lock" in your dashboard Root directory. Otherwise no settings will be loaded!') }} diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 6e1f6f01e..607a692c6 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -255,7 +255,7 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> </li> @endif - @if ((Auth::user()->role == 'admin' || Auth::user()->role == 'moderator') && $ticket_enabled) + @if ((Auth::user()->hasRole("Admin") || Auth::user()->role == 'moderator') && $ticket_enabled) <li class="nav-header">{{ __('Moderation') }}</li> <li class="nav-item"> @@ -274,7 +274,7 @@ class="nav-link @if (Request::routeIs('moderator.ticket.blacklist')) active @end </li> @endif - @if (Auth::user()->role == 'admin') + @if (Auth::user()->hasRole("Admin")) <li class="nav-header">{{ __('Administration') }}</li> <li class="nav-item"> @@ -285,6 +285,13 @@ class="nav-link @if (Request::routeIs('admin.overview.*')) active @endif"> </a> </li> + <li class="nav-item"> + <a href="{{ route('admin.roles.index') }}" + class="nav-link @if (Request::routeIs('admin.roles.*')) active @endif"> + <i class="nav-icon fa fa-user-check"></i> + <p>{{ __('Role Management') }}</p> + </a> + </li> <li class="nav-item"> <a href="{{ route('admin.settings.index') }}" diff --git a/themes/default/views/moderator/ticket/show.blade.php b/themes/default/views/moderator/ticket/show.blade.php index b7d992b52..18c69e2fc 100644 --- a/themes/default/views/moderator/ticket/show.blade.php +++ b/themes/default/views/moderator/ticket/show.blade.php @@ -118,7 +118,7 @@ class="user-image" alt="User Image"> <span class="badge badge-success"> Client </span> @elseif ($ticket->user->role === "moderator") <span class="badge badge-info"> Moderator </span> - @elseif ($ticket->user->role === "admin") + @elseif ($ticket->user->hasRole("Admin")) <span class="badge badge-danger"> Admin </span> @endif </h5> @@ -141,7 +141,7 @@ class="user-image" alt="User Image"> <span class="badge badge-success"> Client </span> @elseif ($ticketcomment->user->role === "moderator") <span class="badge badge-info"> Moderator </span> - @elseif ($ticketcomment->user->role === "admin") + @elseif ($ticketcomment->user->hasRole("Admin")) <span class="badge badge-danger"> Admin </span> @endif </h5> diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 5f5a03147..f8e9a5d40 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -45,7 +45,7 @@ class="row justify-content-center"> <div class="alert alert-danger p-2 m-2"> <h5><i class="icon fas fa-exclamation-circle"></i>{{ __('Error!') }}</h5> <p class="pl-4"> - @if (Auth::user()->role == 'admin') + @if (Auth::user()->hasRole("Admin")) {{ __('Make sure to link your products to nodes and eggs.') }} <br> {{ __('There has to be at least 1 valid product for server creation') }} <a href="{{ route('admin.overview.sync') }}">{{ __('Sync now') }}</a> diff --git a/themes/default/views/ticket/show.blade.php b/themes/default/views/ticket/show.blade.php index cac0070df..663b25f32 100644 --- a/themes/default/views/ticket/show.blade.php +++ b/themes/default/views/ticket/show.blade.php @@ -118,7 +118,7 @@ class="user-image" alt="User Image"> <span class="badge badge-success"> Client </span> @elseif ($ticket->user->role === "moderator") <span class="badge badge-info"> Moderator </span> - @elseif ($ticket->user->role === "admin") + @elseif ($ticket->user->hasRole("Admin")) <span class="badge badge-danger"> Admin </span> @endif </h5> @@ -142,7 +142,7 @@ class="user-image" alt="User Image"> <span class="badge badge-success"> Client </span> @elseif ($ticketcomment->user->role === "moderator") <span class="badge badge-info"> Moderator </span> - @elseif ($ticketcomment->user->role === "admin") + @elseif ($ticketcomment->user->hasRole("Admin")) <span class="badge badge-danger"> Admin </span> @endif </h5> From 490bc64f414d4d58ba39f3017af1b661e2dcbe76 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 02:34:11 +0200 Subject: [PATCH 108/514] basic roles --- app/Http/Controllers/Admin/UserController.php | 29 +++++++++---------- app/Http/Controllers/ProfileController.php | 15 ---------- .../default/views/admin/users/edit.blade.php | 2 +- themes/default/views/profile/index.blade.php | 6 ++-- 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index a7e494a9c..caf8fafd4 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -26,6 +26,7 @@ use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use Spatie\QueryBuilder\QueryBuilder; +use Spatie\Permission\Models\Role; class UserController extends Controller { @@ -108,9 +109,11 @@ public function json(Request $request) */ public function edit(User $user, GeneralSettings $general_settings) { + $roles = Role::all(); return view('admin.users.edit')->with([ 'user' => $user, - 'credits_display_name' => $general_settings->credits_display_name + 'credits_display_name' => $general_settings->credits_display_name, + 'roles' => $roles ]); } @@ -135,6 +138,11 @@ public function update(Request $request, User $user) 'referral_code' => "required|string|min:2|max:32|unique:users,referral_code,{$user->id}", ]); + //update roles + if ($request->roles) { + $user->syncRoles($request->roles); + } + if (isset($this->pterodactyl->getUser($request->input('pterodactyl_id'))['errors'])) { throw ValidationException::withMessages([ 'pterodactyl_id' => [__("User does not exists on pterodactyl's panel")], @@ -329,22 +337,13 @@ public function dataTable(Request $request) '; }) ->editColumn('role', function (User $user) { - switch ($user->role) { - case 'admin': - $badgeColor = 'badge-danger'; - break; - case 'moderator': - $badgeColor = 'badge-info'; - break; - case 'client': - $badgeColor = 'badge-success'; - break; - default: - $badgeColor = 'badge-secondary'; - break; + $html = ''; + + foreach ($user->roles as $role) { + $html .= "<span style='background-color: $role->color' class='badge'>$role->name</span>"; } - return '<span class="badge ' . $badgeColor . '">' . $user->role . '</span>'; + return $html; }) ->editColumn('last_seen', function (User $user) { return $user->last_seen ? $user->last_seen->diffForHumans() : __('Never'); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 5c4293aed..8a0425016 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -26,27 +26,12 @@ public function __construct(PterodactylSettings $ptero_settings) /** Display a listing of the resource. */ public function index(UserSettings $user_settings, DiscordSettings $discord_settings, ReferralSettings $referral_settings) { - switch (Auth::user()->role) { - case 'admin': - $badgeColor = 'badge-danger'; - break; - case 'mod': - $badgeColor = 'badge-info'; - break; - case 'client': - $badgeColor = 'badge-success'; - break; - default: - $badgeColor = 'badge-secondary'; - break; - } return view('profile.index')->with([ 'user' => Auth::user(), 'credits_reward_after_verify_discord' => $user_settings->credits_reward_after_verify_discord, 'force_email_verification' => $user_settings->force_email_verification, 'force_discord_verification' => $user_settings->force_discord_verification, - 'badgeColor' => $badgeColor, 'discord_client_id' => $discord_settings->client_id, 'discord_client_secret' => $discord_settings->client_secret, 'referral_enabled' => $referral_settings->enabled, diff --git a/themes/default/views/admin/users/edit.blade.php b/themes/default/views/admin/users/edit.blade.php index b4c840bfc..221571918 100644 --- a/themes/default/views/admin/users/edit.blade.php +++ b/themes/default/views/admin/users/edit.blade.php @@ -105,7 +105,7 @@ class="custom-select @error('role') is-invalid @enderror" @if(isset($user) && $user->roles->contains($role)) selected @endif value="{{$role->id}}">{{$role->name}}</option> @endforeach - </select> + </select> </div> </div> <div class="form-group"> diff --git a/themes/default/views/profile/index.blade.php b/themes/default/views/profile/index.blade.php index 82d09e145..6fe0208b6 100644 --- a/themes/default/views/profile/index.blade.php +++ b/themes/default/views/profile/index.blade.php @@ -118,8 +118,10 @@ class="fa fa-user-check mr-2"></i> @endif </div> - <div class="text-center text-sm-right"><span - class="badge {{$badgeColor}}">{{ $user->role }}</span> + <div class="text-center text-sm-right"> + @foreach ($user->roles as $role) + <span style='background-color: {{$role->color}}' class='badge'>{{$role->name}}</span> + @endforeach <div class="text-muted"> <small>{{ $user->created_at->isoFormat('LL') }}</small> </div> From 83d7590b600ad77c6305126b923c6c2ef03c4673 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 12:13:01 +0200 Subject: [PATCH 109/514] fix installer, new users get role --- .../Controllers/Auth/RegisterController.php | 2 + .../2023_04_03_231829_update_users_table.php | 2 +- .../2023_04_29_233120_drop_roles.php | 51 ------------------- database/seeders/PermissionsSeeder.php | 11 ++-- public/install/forms.php | 10 ++-- 5 files changed, 18 insertions(+), 58 deletions(-) delete mode 100644 database/migrations/2023_04_29_233120_drop_roles.php diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 29a4b6e0a..9f1e8019b 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -139,6 +139,8 @@ protected function create(array $data) ]); + $user->syncRoles(4); + $response = $this->pterodactyl->application->post('/application/users', [ 'external_id' => $user->pterodactyl_id, 'username' => $user->name, diff --git a/database/migrations/2023_04_03_231829_update_users_table.php b/database/migrations/2023_04_03_231829_update_users_table.php index 3caf98aa0..df2497417 100644 --- a/database/migrations/2023_04_03_231829_update_users_table.php +++ b/database/migrations/2023_04_03_231829_update_users_table.php @@ -26,7 +26,7 @@ public function up() public function down() { Schema::table('users', function (Blueprint $table) { - $table->integer('pterodactyl_id')->nullable->change(); + $table->integer('pterodactyl_id')->nullable()->change(); }); } }; diff --git a/database/migrations/2023_04_29_233120_drop_roles.php b/database/migrations/2023_04_29_233120_drop_roles.php deleted file mode 100644 index 3f3657077..000000000 --- a/database/migrations/2023_04_29_233120_drop_roles.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -use App\Models\User; -use Illuminate\Database\Migrations\Migration; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\Schema; - -return new class extends Migration -{ - /** - * Run the migrations. - * - * @return void - */ - public function up() - { - Artisan::call('db:seed', [ - '--class' => 'PermissionsSeeder', - ]); - - Schema::table('users', function ($table) { - $table->dropColumn('role'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('users', function($table) { - $table->string('role')->default('member'); - }); - - $users = User::with('roles')->get(); - foreach($users as $user){ - if($user->hasRole(1)){ - $user->role = "admin"; - }elseif ($user->hasRole(3)){ - $user->role = "client"; - }else{ - $user->role = "member"; - } - $user->save(); - } - - } -}; diff --git a/database/seeders/PermissionsSeeder.php b/database/seeders/PermissionsSeeder.php index 7d6ebc062..e688bdf07 100644 --- a/database/seeders/PermissionsSeeder.php +++ b/database/seeders/PermissionsSeeder.php @@ -32,9 +32,14 @@ public function run() $admin->syncRoles(1); } - $admins = User::where("role","client")->get(); - foreach($admins as $admin) { - $admin->syncRoles(3); + $mods = User::where("role","moderator")->get(); + foreach($mods as $mod) { + $mod->syncRoles(2); + } + + $clients = User::where("role","client")->get(); + foreach($clients as $client) { + $client->syncRoles(3); } diff --git a/public/install/forms.php b/public/install/forms.php index d6d72b92c..726e22ec3 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -1,5 +1,7 @@ <?php - +ini_set('display_errors', 1); +ini_set('display_startup_errors', 1); +error_reporting(E_ALL); use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\PHPMailer; @@ -77,6 +79,7 @@ $logs .= run_console('php artisan storage:link'); $logs .= run_console('php artisan migrate --seed --force'); $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); + $logs .= run_console('php artisan db:seed --class=PermissionsSeeder --force'); wh_log($logs, 'debug'); @@ -292,8 +295,9 @@ } $random = generateRandomString(); - $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; - $query2 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\Models\User', '1')"; + + $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; + $query2 = "INSERT INTO `" . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\\\Models\\\User', '1')"; if ($db->query($query1) && $db->query($query2)) { wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID, 'info'); header('LOCATION: index.php?step=7'); From fd2e65f8b318fbc0250e8ab19f426653ec18224c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 12:15:42 +0200 Subject: [PATCH 110/514] fix rolecontroller --- app/Http/Controllers/Admin/RoleController.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index 22a8c113f..4962b2658 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Models\User; use Exception; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; @@ -105,7 +106,7 @@ public function update(Request $request, Role $role) } } - if($role->id == 3 || $role->id == 1 || $role->id == 4){ //dont let the user change the names of these roles + if($role->id == 1 || $role->id == 3 || $role->id == 4){ //dont let the user change the names of these roles $role->update([ 'color' => $request->color ]); @@ -135,14 +136,15 @@ public function update(Request $request, Role $role) public function destroy(Role $role) { - if($role->id == 3 || $role->id == 1 || $role->id == 2){ //cannot delete the hard coded roles + if($role->id == 1 || $role->id == 3 || $role->id == 4){ //cannot delete the hard coded roles return back()->with("error","You cannot delete that role"); } $users = User::role($role)->get(); foreach($users as $user){ - $user->syncRoles(['Member']); + //$user->syncRoles(['Member']); + $user->syncRoles(4); } $role->delete(); From c9c991d16406ab580befbe0a0a0e5fa02b87c1d5 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 12:17:02 +0200 Subject: [PATCH 111/514] allow editing name of member and admin. should be fine. we are using IDs --- app/Http/Controllers/Admin/RoleController.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index 4962b2658..d5d474af8 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -106,26 +106,26 @@ public function update(Request $request, Role $role) } } - if($role->id == 1 || $role->id == 3 || $role->id == 4){ //dont let the user change the names of these roles - $role->update([ - 'color' => $request->color - ]); - }else{ + //if($role->id == 1 || $role->id == 3 || $role->id == 4){ //dont let the user change the names of these roles + // $role->update([ + // 'color' => $request->color + // ]); + //}else{ $role->update([ 'name' => $request->name, 'color' => $request->color ]); - } + //} - if($role->id == 1){ - return redirect()->route('admin.roles.index')->with('success', __('Role updated. Name and Permissions of this Role cannot be changed')); - }elseif($role->id == 4 || $role->id == 3){ - return redirect()->route('admin.roles.index')->with('success', __('Role updated. Name of this Role cannot be changed')); - }else{ + //if($role->id == 1){ + // return redirect()->route('admin.roles.index')->with('success', __('Role updated. Name and Permissions of this Role cannot be changed')); + //}elseif($role->id == 4 || $role->id == 3){ + // return redirect()->route('admin.roles.index')->with('success', __('Role updated. Name of this Role cannot be changed')); + // }else{ return redirect() ->route('admin.roles.index') ->with('success', __('Role saved')); - } + //} } /** From e8c8d1c68d5910b1bf9ee5694c6a4f4928e06ec2 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 30 Apr 2023 12:21:01 +0200 Subject: [PATCH 112/514] Use IDs instead of Role Names --- app/Http/Middleware/isAdmin.php | 3 ++- themes/default/views/layouts/main.blade.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Http/Middleware/isAdmin.php b/app/Http/Middleware/isAdmin.php index 1bf4f55af..6e8202a43 100644 --- a/app/Http/Middleware/isAdmin.php +++ b/app/Http/Middleware/isAdmin.php @@ -18,7 +18,8 @@ class isAdmin */ public function handle(Request $request, Closure $next) { - if (Auth::user() && Auth::user()->hasRole("Admin")) { + //if (Auth::user() && Auth::user()->hasRole("Admin")) { + if (Auth::user() && Auth::user()->hasRole(1)) { return $next($request); } diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 607a692c6..6925c9366 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -255,7 +255,7 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> </li> @endif - @if ((Auth::user()->hasRole("Admin") || Auth::user()->role == 'moderator') && $ticket_enabled) + @if ((Auth::user()->hasRole(1) || Auth::user()->role == 'moderator') && $ticket_enabled) <li class="nav-header">{{ __('Moderation') }}</li> <li class="nav-item"> @@ -274,7 +274,7 @@ class="nav-link @if (Request::routeIs('moderator.ticket.blacklist')) active @end </li> @endif - @if (Auth::user()->hasRole("Admin")) + @if (Auth::user()->hasRole(1)) <li class="nav-header">{{ __('Administration') }}</li> <li class="nav-item"> From eb3afbfaaba9660b1de264e2b68d801e72a13191 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 3 May 2023 09:53:41 +0200 Subject: [PATCH 113/514] full user permissions --- app/Http/Controllers/Admin/PaymentController.php | 3 +++ app/Http/Controllers/Admin/RoleController.php | 3 +++ app/Http/Controllers/ServerController.php | 8 ++++++++ app/Http/Controllers/TicketsController.php | 4 ++++ config/permission.php | 2 +- themes/default/views/admin/roles/index.blade.php | 4 +++- .../default/views/admin/settings/index.blade.php | 4 ++++ themes/default/views/layouts/main.blade.php | 2 ++ themes/default/views/profile/index.blade.php | 6 +++--- themes/default/views/servers/index.blade.php | 16 ++++++++-------- themes/default/views/servers/settings.blade.php | 2 +- themes/default/views/store/index.blade.php | 2 +- themes/default/views/ticket/index.blade.php | 4 ++-- 13 files changed, 43 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 25d2c9ceb..02d91bde9 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -23,6 +23,7 @@ class PaymentController extends Controller { + const BUY_PERMISSION = 'user.shop.buy'; /** * @return Application|Factory|View */ @@ -41,6 +42,8 @@ public function index(LocaleSettings $locale_settings) */ public function checkOut(ShopProduct $shopProduct, GeneralSettings $general_settings) { + $this->checkPermission(self::BUY_PERMISSION); + $discount = PartnerDiscount::getDiscount(); $price = $shopProduct->price - ($shopProduct->price * $discount / 100); diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index d5d474af8..ed4194bee 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -164,6 +164,9 @@ public function dataTable() return datatables($query) + ->editColumn('id', function (Role $role) { + return $role->id; + }) ->addColumn('actions', function (Role $role) { return ' <a title="Edit" href="'.route("admin.roles.edit", $role).'" class="btn btn-sm btn-info"><i diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 9683e2b3e..acd4392e3 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -15,6 +15,7 @@ use App\Classes\PterodactylClient; use App\Settings\GeneralSettings; use Exception; +use GuzzleHttp\Promise\Create; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Client\Response; use Illuminate\Http\RedirectResponse; @@ -24,6 +25,9 @@ class ServerController extends Controller { + const CREATE_PERMISSION = 'user.server.create'; + const UPGRADE_PERMISSION = 'user.server.upgrade'; + private $pterodactyl; public function __construct(PterodactylSettings $ptero_settings) @@ -81,6 +85,8 @@ public function index(GeneralSettings $general_settings, PterodactylSettings $pt /** Show the form for creating a new resource. */ public function create(UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $general_settings) { + $this->checkPermission(self::CREATE_PERMISSION); + $validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings); if (!is_null($validate_configuration)) { @@ -316,6 +322,8 @@ public function show(Server $server, ServerSettings $server_settings, GeneralSet public function upgrade(Server $server, Request $request) { + $this->checkPermission(self::UPGRADE_PERMISSION); + if ($server->user_id != Auth::user()->id) { return redirect()->route('servers.index'); } diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index 754241edd..d5912e435 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -21,6 +21,8 @@ class TicketsController extends Controller { + const READ_PERMISSION = 'user.ticket.read'; + const WRITE_PERMISSION = 'user.ticket.write'; public function index(LocaleSettings $locale_settings) { return view('ticket.index', [ @@ -74,6 +76,7 @@ public function store(Request $request, TicketSettings $ticket_settings) public function show($ticket_id, PterodactylSettings $ptero_settings) { + $this->checkPermission(self::READ_PERMISSION); try { $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail(); } catch (Exception $e) { @@ -118,6 +121,7 @@ public function reply(Request $request) public function create() { + $this->checkPermission(self::WRITE_PERMISSION); //check in blacklist $check = TicketBlacklist::where('user_id', Auth::user()->id)->first(); if ($check && $check->status == 'True') { diff --git a/config/permission.php b/config/permission.php index 5b6e184c3..5aeaab7c8 100644 --- a/config/permission.php +++ b/config/permission.php @@ -133,7 +133,7 @@ * By default wildcard permission lookups are disabled. */ - 'enable_wildcard_permission' => false, + 'enable_wildcard_permission' => true, 'cache' => [ diff --git a/themes/default/views/admin/roles/index.blade.php b/themes/default/views/admin/roles/index.blade.php index 71c88d92f..0bc9d3a2e 100644 --- a/themes/default/views/admin/roles/index.blade.php +++ b/themes/default/views/admin/roles/index.blade.php @@ -18,6 +18,7 @@ class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> <table id="datatable" class="table table-striped"> <thead> <tr> + <th>{{__("ID")}}</th> <th>{{__("Name")}}</th> <th>{{__("User count")}}</th> <th>{{__("Permissions count")}}</th> @@ -40,10 +41,11 @@ class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("SETTINGS::LOCALE:DATATABLES")}}.json' }, processing: true, - serverSide: false, //increases loading times too much? change back to "true" if it does + serverSide: true, //increases loading times too much? change back to "true" if it does stateSave: true, ajax: "{{route('admin.roles.datatable')}}", columns: [ + {data: 'id'}, {data: 'name'}, {data: 'usercount'}, {data: 'permissionscount'}, diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 89249eac9..134e0a959 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -159,6 +159,9 @@ class="custom-select w-100" name="{{ $key }}" </div> </div> @endforeach + + <!-- TODO: Display this only on the General tab + <div class="row"> <div class="col-4 d-flex align-items-center"> <label for="recaptcha_preview">{{__("ReCAPTCHA Preview")}}</label> @@ -179,6 +182,7 @@ class="custom-select w-100" name="{{ $key }}" </div> </div> </div> + --> <div class="row"> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 6925c9366..c72187bd9 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -246,6 +246,7 @@ class="nav-link @if (Request::routeIs('store.*') || Request::routeIs('checkout') @endif @php($ticket_enabled = app(App\Settings\TicketSettings::class)->enabled) @if ($ticket_enabled) + @canany(["user.ticket.read", "user.ticket.write"]) <li class="nav-item"> <a href="{{ route('ticket.index') }}" class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> @@ -253,6 +254,7 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> <p>{{ __('Support Ticket') }}</p> </a> </li> + @endcanany @endif @if ((Auth::user()->hasRole(1) || Auth::user()->role == 'moderator') && $ticket_enabled) diff --git a/themes/default/views/profile/index.blade.php b/themes/default/views/profile/index.blade.php index 6fe0208b6..459384379 100644 --- a/themes/default/views/profile/index.blade.php +++ b/themes/default/views/profile/index.blade.php @@ -101,7 +101,7 @@ class="fa fa-coins mr-2"></i>{{ $user->Credits() }}</span> </div> @if($referral_enabled) - @if(($referral_allowed === "client" && $user->role != "member") || $referral_allowed === "everyone") + @can("user.referral") <div class="mt-1"> <span class="badge badge-success"><i class="fa fa-user-check mr-2"></i> @@ -112,8 +112,8 @@ class="fa fa-user-check mr-2"></i> @else <span class="badge badge-warning"><i class="fa fa-user-check mr-2"></i> - {{_("Make a purchase to reveal your referral-URL")}}</span> - @endif + {{_("You can not see your Referral Code")}}</span> + @endcan </div> @endif </div> diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index 4b9b7df1e..df47ae53f 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -27,17 +27,17 @@ <!-- CUSTOM CONTENT --> <div class="d-flex justify-content-md-start justify-content-center mb-3 "> - <a @if (Auth::user()->Servers->count() >= Auth::user()->server_limit) - disabled="disabled" title="Server limit reached!" - @endif href="{{ route('servers.create') }}" - class="btn - @if (Auth::user()->Servers->count() >= Auth::user()->server_limit) disabled - @endif btn-primary"><i - class="fa fa-plus mr-2"></i> + <a @if (Auth::user()->Servers->count() >= Auth::user()->server_limit) disabled="disabled" title="Server limit reached!" @endif + @cannot("user.server.create") disabled="disabled" title="No Permission!" @endcannot + href="{{ route('servers.create') }}" class="btn + @if (Auth::user()->Servers->count() >= Auth::user()->server_limit) disabled @endif + @cannot("user.server.create") disabled @endcannot + btn-primary"> + <i class="fa fa-plus mr-2"></i> {{ __('Create Server') }} </a> @if (Auth::user()->Servers->count() > 0 && !empty($phpmyadmin_url)) - <a + <a href="{{ $phpmyadmin_url }}" target="_blank" class="btn btn-secondary ml-2"><i title="manage" class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span> diff --git a/themes/default/views/servers/settings.blade.php b/themes/default/views/servers/settings.blade.php index 2ec6bb279..4ef717443 100644 --- a/themes/default/views/servers/settings.blade.php +++ b/themes/default/views/servers/settings.blade.php @@ -222,7 +222,7 @@ <div class="card-footer"> <div class="col-md-12 text-center"> <!-- Upgrade Button trigger modal --> - @if($server_enable_upgrade) + @if($server_enable_upgrade && Auth::user()->can("user.server.upgrade")) <button type="button" data-toggle="modal" data-target="#UpgradeModal{{ $server->id }}" target="__blank" class="btn btn-info btn-md"> <i class="fas fa-upload mr-2"></i> diff --git a/themes/default/views/store/index.blade.php b/themes/default/views/store/index.blade.php index 31393c199..8fd58f5fd 100644 --- a/themes/default/views/store/index.blade.php +++ b/themes/default/views/store/index.blade.php @@ -61,7 +61,7 @@ {{ $product->display }} </td> <td><a href="{{ route('checkout', $product->id) }}" - class="btn btn-info">{{ __('Purchase') }}</a> + class="btn btn-info @cannot('user.shop.buy') disabled @endcannot">{{ __('Purchase') }}</a> </td> </tr> @endforeach diff --git a/themes/default/views/ticket/index.blade.php b/themes/default/views/ticket/index.blade.php index d2e66c80b..0c1d40988 100644 --- a/themes/default/views/ticket/index.blade.php +++ b/themes/default/views/ticket/index.blade.php @@ -30,8 +30,8 @@ <div class="card-header"> <div class="d-flex justify-content-between"> <h5 class="card-title"><i class="fas fa-ticket-alt mr-2"></i>{{__('My Ticket')}}</h5> - <a href="{{route('ticket.new')}}" class="btn btn-sm btn-primary"><i - class="fas fa-plus mr-1"></i>{{__('New Ticket')}}</a> + <a href="{{route('ticket.new')}}" class="btn btn-sm btn-primary @cannot("user.ticket.write")) disabled @endcannot"> + <i class="fas fa-plus mr-1"></i>{{__('New Ticket')}}</a> </div> </div> <div class="card-body table-responsive"> From 7d93d45197baa4fc5afdd1132c0319f397a876f2 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 3 May 2023 14:52:34 +0200 Subject: [PATCH 114/514] Update 2023_02_01_182158_create_website_settings.php --- .../settings/2023_02_01_182158_create_website_settings.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/database/settings/2023_02_01_182158_create_website_settings.php b/database/settings/2023_02_01_182158_create_website_settings.php index 013c85f87..c7e8fa515 100644 --- a/database/settings/2023_02_01_182158_create_website_settings.php +++ b/database/settings/2023_02_01_182158_create_website_settings.php @@ -14,10 +14,7 @@ public function up(): void $this->migrator->add( 'website.motd_message', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:MOTD_MESSAGE") : - '<h1 style="text-align: center;"><img style="display: block; margin-left: auto; margin-right: auto;" src="https://ctrlpanel.gg/img/controlpanel.png" alt="" width="200" height="200"><span style="font-size: 36pt;">Controlpanel.gg</span></h1> - <p><span style="font-size: 18pt;">Thank you for using our Software</span></p> - <p><span style="font-size: 18pt;">If you have any questions, make sure to join our <a href="https://discord.com/invite/4Y6HjD2uyU" target="_blank" rel="noopener">Discord</a></span></p> - <p><span style="font-size: 10pt;">(you can change this message in the <a href="admin/settings#system">Settings</a> )</span></p>' + '<h1 style=\"text-align: center;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https:\/\/ctrlpanel.gg\/img\/controlpanel.png\" alt=\"\" width=\"200\" height=\"200\"><span style=\"font-size: 36pt;\">Controlpanel.gg<\/span><\/h1>\r\n<p><span style=\"font-size: 18pt;\">Thank you for using our Software<\/span><\/p>\r\n<p><span style=\"font-size: 18pt;\">If you have any questions, make sure to join our <a href=\"https:\/\/discord.com\/invite\/4Y6HjD2uyU\" target=\"_blank\" rel=\"noopener\">Discord<\/a><\/span><\/p>\r\n<p><span style=\"font-size: 10pt;\">(you can change this message in the <a href=\"admin\/settings#system\">Settings<\/a> )<\/span><\/p>' ); $this->migrator->add('website.show_imprint', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_IMPRINT") : false); $this->migrator->add('website.show_privacy', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_PRIVACY") : false); From a4280a6fba9937d9adab40c80e769e6df751b31e Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Wed, 3 May 2023 15:17:13 +0200 Subject: [PATCH 115/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Path=20seperator?= =?UTF-8?q?=20replacement=20->=20be=20filesystem=20aware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Helpers/ExtensionHelper.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/Helpers/ExtensionHelper.php b/app/Helpers/ExtensionHelper.php index 6b59c1e4d..117e54e34 100644 --- a/app/Helpers/ExtensionHelper.php +++ b/app/Helpers/ExtensionHelper.php @@ -18,8 +18,9 @@ public static function getAllExtensions() foreach ($extensionNamespaces as $extensionNamespace) { $extensions = array_merge($extensions, glob($extensionNamespace . '/*', GLOB_ONLYDIR)); } + // remove base path from every extension but keep app/Extensions/... - $extensions = array_map(fn ($item) => str_replace('/', '\\', str_replace(app_path() . '/', 'App/', $item)), $extensions); + $extensions = array_map(fn ($item) => str_replace(app_path() . '/', 'App/', $item), $extensions); return $extensions; } @@ -33,7 +34,7 @@ public static function getAllExtensionsByNamespace(string $namespace) { $extensions = glob(app_path() . '/Extensions/' . $namespace . '/*', GLOB_ONLYDIR); // remove base path from every extension but keep app/Extensions/... - $extensions = array_map(fn ($item) => str_replace('/', '\\', str_replace(app_path() . '/', 'App/', $item)), $extensions); + $extensions = array_map(fn ($item) => str_replace(app_path() . '/', 'App/', $item), $extensions); return $extensions; } @@ -60,6 +61,9 @@ public static function getExtension(string $extensionName) public static function getAllExtensionClasses() { $extensions = self::getAllExtensions(); + + // replace all slashes with backslashes + $extensions = array_map(fn ($item) => str_replace('/', '\\', $item), $extensions); // add the ExtensionClass to the end of the namespace $extensions = array_map(fn ($item) => $item . '\\' . basename($item) . 'Extension', $extensions); // filter out non existing extension classes @@ -76,6 +80,9 @@ public static function getAllExtensionClasses() public static function getAllExtensionClassesByNamespace(string $namespace) { $extensions = self::getAllExtensionsByNamespace($namespace); + + // replace all slashes with backslashes + $extensions = array_map(fn ($item) => str_replace('/', '\\', $item), $extensions); // add the ExtensionClass to the end of the namespace $extensions = array_map(fn ($item) => $item . '\\' . basename($item) . 'Extension', $extensions); // filter out non existing extension classes @@ -177,10 +184,13 @@ public static function getAllExtensionSettingsClasses() { $extensions = self::getAllExtensions(); + $settings = []; foreach ($extensions as $extension) { - $extensionName = basename($extension); + + // replace all slashes with backslashes + $extension = str_replace('/', '\\', $extension); $settingsClass = $extension . '\\' . $extensionName . 'Settings'; if (class_exists($settingsClass)) { $settings[] = $settingsClass; @@ -193,6 +203,9 @@ public static function getAllExtensionSettingsClasses() public static function getExtensionSettings(string $extensionName) { $extension = self::getExtension($extensionName); + // replace all slashes with backslashes + $extension = str_replace('/', '\\', $extension); + $settingClass = $extension . '\\' . $extensionName . 'Settings'; if (class_exists($settingClass)) { @@ -207,6 +220,6 @@ public static function getExtensionSettings(string $extensionName) */ private static function extensionNameToPath(string $extensionName) { - return app_path() . '/' . str_replace('\\', '/', str_replace('App\\', '', $extensionName)); + return app_path() . '/' . str_replace('App/', '', $extensionName); } } From 7754041532ca55f534ba17dd92bdff6519ce0b3c Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Wed, 3 May 2023 15:17:35 +0200 Subject: [PATCH 116/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20hotfix=20migratio?= =?UTF-8?q?ns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/2023_03_04_135248_create_pay_pal_settings.php | 4 ++++ .../migrations/2023_03_04_181917_create_stripe_settings.php | 4 ++++ database/migrations/2023_04_03_231829_update_users_table.php | 3 ++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php b/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php index 5792abec4..db7d0bdd5 100644 --- a/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php +++ b/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php @@ -77,6 +77,10 @@ public function getOldValue(string $key) // Always get the first value of the key. $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); + if (is_null($old_value)) { + return null; + } + // Handle the old values to return without it being a string in all cases. if ($old_value->type === "string" || $old_value->type === "text") { if (is_null($old_value->value)) { diff --git a/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php b/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php index a8145a7c8..0ed72922c 100644 --- a/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php +++ b/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php @@ -75,6 +75,10 @@ public function getOldValue(string $key) // Always get the first value of the key. $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); + if (is_null($old_value)) { + return null; + } + // Handle the old values to return without it being a string in all cases. if ($old_value->type === "string" || $old_value->type === "text") { if (is_null($old_value->value)) { diff --git a/database/migrations/2023_04_03_231829_update_users_table.php b/database/migrations/2023_04_03_231829_update_users_table.php index 3caf98aa0..20ca26078 100644 --- a/database/migrations/2023_04_03_231829_update_users_table.php +++ b/database/migrations/2023_04_03_231829_update_users_table.php @@ -26,7 +26,8 @@ public function up() public function down() { Schema::table('users', function (Blueprint $table) { - $table->integer('pterodactyl_id')->nullable->change(); + // make the column nullable again + $table->integer('pterodactyl_id')->nullable()->change(); }); } }; From 04a4f2ab96b310cae7be61da0538b3aa1ff410f3 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 3 May 2023 15:44:22 +0200 Subject: [PATCH 117/514] disable invoices by default --- app/Helpers/ExtensionHelper.php | 4 ++-- .../settings/2023_02_01_182021_create_invoice_settings.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Helpers/ExtensionHelper.php b/app/Helpers/ExtensionHelper.php index 117e54e34..f465078b0 100644 --- a/app/Helpers/ExtensionHelper.php +++ b/app/Helpers/ExtensionHelper.php @@ -64,7 +64,7 @@ public static function getAllExtensionClasses() // replace all slashes with backslashes $extensions = array_map(fn ($item) => str_replace('/', '\\', $item), $extensions); - // add the ExtensionClass to the end of the namespace + // add the ExtensionClass to the end of the namespace $extensions = array_map(fn ($item) => $item . '\\' . basename($item) . 'Extension', $extensions); // filter out non existing extension classes $extensions = array_filter($extensions, fn ($item) => class_exists($item)); @@ -104,7 +104,7 @@ public static function getExtensionClass(string $extensionName) if (!(basename($extension) == $extensionName)) { continue; } - + $extension = str_replace('/', '\\', $extension); $extensionClass = $extension . '\\' . $extensionName . 'Extension'; return $extensionClass; } diff --git a/database/settings/2023_02_01_182021_create_invoice_settings.php b/database/settings/2023_02_01_182021_create_invoice_settings.php index 3fb835379..8569c3ff4 100644 --- a/database/settings/2023_02_01_182021_create_invoice_settings.php +++ b/database/settings/2023_02_01_182021_create_invoice_settings.php @@ -16,7 +16,7 @@ public function up(): void $this->migrator->add('invoice.company_phone', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_PHONE') : ''); $this->migrator->add('invoice.company_vat', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_VAT') : ''); $this->migrator->add('invoice.company_website', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_WEBSITE') : ''); - $this->migrator->add('invoice.enabled', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:ENABLED') : true); + $this->migrator->add('invoice.enabled', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:ENABLED') : false); $this->migrator->add('invoice.prefix', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:PREFIX') : 'INV'); } From 5d288cd07078f9fbfc873f22af47e146b1601775 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 3 May 2023 16:05:55 +0200 Subject: [PATCH 118/514] Update UserPayment.php --- app/Listeners/UserPayment.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Listeners/UserPayment.php b/app/Listeners/UserPayment.php index 4fbe9ba80..3417a7ce2 100644 --- a/app/Listeners/UserPayment.php +++ b/app/Listeners/UserPayment.php @@ -35,7 +35,7 @@ public function __construct(UserSettings $user_settings, ReferralSettings $refer $this->referral_always_give_commission = $referral_settings->always_give_commission; $this->credits_display_name = $general_settings->credits_display_name; } - + /** * Handle the event. * @@ -79,8 +79,8 @@ public function handle(PaymentEvent $event) } } //update role give Referral-reward - if ($user->role == 'member') { - $user->update(['role' => 'client']); + if ($user->hasRole(4)) { + $user->syncRoles(3); //give referral commission only on first purchase if (($this->referral_mode === "commission" || $this->referral_mode === "both") && $shopProduct->type == "Credits" && !$this->referral_always_give_commission) { From 804a800d4f49f0f833bbaaef6d1add568f2a85bc Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 3 May 2023 16:16:50 +0200 Subject: [PATCH 119/514] full frontend perms showing roles --- app/Console/Commands/MakeUserCommand.php | 2 ++ app/Http/Middleware/isMod.php | 2 +- routes/web.php | 2 +- .../default/views/admin/users/show.blade.php | 15 +++--------- .../views/moderator/ticket/show.blade.php | 24 +++++-------------- themes/default/views/ticket/show.blade.php | 24 +++++-------------- 6 files changed, 19 insertions(+), 50 deletions(-) diff --git a/app/Console/Commands/MakeUserCommand.php b/app/Console/Commands/MakeUserCommand.php index 0932f9f27..fb1366fe7 100644 --- a/app/Console/Commands/MakeUserCommand.php +++ b/app/Console/Commands/MakeUserCommand.php @@ -101,6 +101,8 @@ public function handle(PterodactylSettings $ptero_settings) ['Referral code', $user->referral_code], ]); + $user->syncRoles(1); + return 1; } } diff --git a/app/Http/Middleware/isMod.php b/app/Http/Middleware/isMod.php index 8c5453a24..51d6db851 100644 --- a/app/Http/Middleware/isMod.php +++ b/app/Http/Middleware/isMod.php @@ -18,7 +18,7 @@ class isMod */ public function handle(Request $request, Closure $next) { - if (Auth::user() && Auth::user()->role == 'moderator' || Auth::user() && Auth::user()->hasRole("Admin")) { + if (Auth::user() && Auth::user()->role == 'moderator' || Auth::user() && Auth::user()->hasRole(1)) { return $next($request); } diff --git a/routes/web.php b/routes/web.php index f13a33a99..c7c6a9de1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -113,7 +113,7 @@ Route::post('ticket/new', [TicketsController::class, 'store'])->middleware(['throttle:ticket-new'])->name('ticket.new.store'); Route::get('ticket/show/{ticket_id}', [TicketsController::class, 'show'])->name('ticket.show'); Route::post('ticket/reply', [TicketsController::class, 'reply'])->middleware(['throttle:ticket-reply'])->name('ticket.reply'); - Route::post('ticket/close/{ticket_id}', [TicketsController::class, 'close'])->name('ticket.close'); + Route::post('ticket/status/{ticket_id}', [TicketsController::class, 'changeStatus'])->name('ticket.changeStatus'); //admin diff --git a/themes/default/views/admin/users/show.blade.php b/themes/default/views/admin/users/show.blade.php index 5d2da4120..3ab0d62ee 100644 --- a/themes/default/views/admin/users/show.blade.php +++ b/themes/default/views/admin/users/show.blade.php @@ -74,18 +74,9 @@ <label>{{ __('Role') }}</label> </div> <div class="col-lg-8"> - <span style="max-width: 250px;" - class="d-inline-block text-truncate badge - @if ($user->hasRole("Admin")) badge-danger - @elseif ($user->role == 'moderator') - badge-info - @elseif ($user->role == 'client') - badge-success - @else - badge-secondary @endif - "> - {{ $user->role }} - </span> + @foreach ($user->roles as $role) + <span style='background-color: {{$role->color}}' class='badge'>{{$role->name}}</span> + @endforeach </div> </div> </div> diff --git a/themes/default/views/moderator/ticket/show.blade.php b/themes/default/views/moderator/ticket/show.blade.php index 18c69e2fc..3bec1feba 100644 --- a/themes/default/views/moderator/ticket/show.blade.php +++ b/themes/default/views/moderator/ticket/show.blade.php @@ -112,15 +112,9 @@ class="fas fa-times"></i>{{__("Close")}}</button> src="https://www.gravatar.com/avatar/{{ md5(strtolower($ticket->user->email)) }}?s=25" class="user-image" alt="User Image"> <a href="/admin/users/{{$ticket->user->id}}">{{ $ticket->user->name }}</a> - @if($ticket->user->role === "member") - <span class="badge badge-secondary"> Member </span> - @elseif ($ticket->user->role === "client") - <span class="badge badge-success"> Client </span> - @elseif ($ticket->user->role === "moderator") - <span class="badge badge-info"> Moderator </span> - @elseif ($ticket->user->hasRole("Admin")) - <span class="badge badge-danger"> Admin </span> - @endif + @foreach ($ticket->user->roles as $role) + <span style='background-color: {{$role->color}}' class='badge'>{{$role->name}}</span> + @endforeach </h5> <span class="badge badge-primary">{{ $ticket->created_at->diffForHumans() }}</span> </div> @@ -135,15 +129,9 @@ class="user-image" alt="User Image"> src="https://www.gravatar.com/avatar/{{ md5(strtolower($ticketcomment->user->email)) }}?s=25" class="user-image" alt="User Image"> <a href="/admin/users/{{$ticketcomment->user->id}}">{{ $ticketcomment->user->name }}</a> - @if($ticketcomment->user->role === "member") - <span class="badge badge-secondary"> Member </span> - @elseif ($ticketcomment->user->role === "client") - <span class="badge badge-success"> Client </span> - @elseif ($ticketcomment->user->role === "moderator") - <span class="badge badge-info"> Moderator </span> - @elseif ($ticketcomment->user->hasRole("Admin")) - <span class="badge badge-danger"> Admin </span> - @endif + @foreach ($ticketcomment->user->roles as $role) + <span style='background-color: {{$role->color}}' class='badge'>{{$role->name}}</span> + @endforeach </h5> <span class="badge badge-primary">{{ $ticketcomment->created_at->diffForHumans() }}</span> </div> diff --git a/themes/default/views/ticket/show.blade.php b/themes/default/views/ticket/show.blade.php index 663b25f32..5a936afa9 100644 --- a/themes/default/views/ticket/show.blade.php +++ b/themes/default/views/ticket/show.blade.php @@ -112,15 +112,9 @@ class="fas fa-times"></i>{{__("Close")}}</button> src="https://www.gravatar.com/avatar/{{ md5(strtolower($ticket->user->email)) }}?s=25" class="user-image" alt="User Image"> <a href="/admin/users/{{$ticket->user->id}}">{{ $ticket->user->name }} </a> - @if($ticket->user->role === "member") - <span class="badge badge-secondary"> Member </span> - @elseif ($ticket->user->role === "client") - <span class="badge badge-success"> Client </span> - @elseif ($ticket->user->role === "moderator") - <span class="badge badge-info"> Moderator </span> - @elseif ($ticket->user->hasRole("Admin")) - <span class="badge badge-danger"> Admin </span> - @endif + @foreach ($ticket->user->roles as $role) + <span style='background-color: {{$role->color}}' class='badge'>{{$role->name}}</span> + @endforeach </h5> <span class="badge badge-primary">{{ $ticket->created_at->diffForHumans() }}</span> @@ -136,15 +130,9 @@ class="badge badge-primary">{{ $ticket->created_at->diffForHumans() }}</span> src="https://www.gravatar.com/avatar/{{ md5(strtolower($ticketcomment->user->email)) }}?s=25" class="user-image" alt="User Image"> <a href="/admin/users/{{$ticketcomment->user->id}}">{{ $ticketcomment->user->name }}</a> - @if($ticketcomment->user->role === "member") - <span class="badge badge-secondary"> Member </span> - @elseif ($ticketcomment->user->role === "client") - <span class="badge badge-success"> Client </span> - @elseif ($ticketcomment->user->role === "moderator") - <span class="badge badge-info"> Moderator </span> - @elseif ($ticketcomment->user->hasRole("Admin")) - <span class="badge badge-danger"> Admin </span> - @endif + @foreach ($ticketcomment->user->roles as $role) + <span style='background-color: {{$role->color}}' class='badge'>{{$role->name}}</span> + @endforeach </h5> <span class="badge badge-primary">{{ $ticketcomment->created_at->diffForHumans() }}</span> From 4c780deb0293db147544a2353e233058cd4dee7b Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 01:11:55 +0200 Subject: [PATCH 120/514] All permissions except Settings --- .../Admin/ActivityLogController.php | 4 + .../Admin/ApplicationApiController.php | 9 ++ .../Controllers/Admin/LegalController.php | 6 + .../Controllers/Admin/OverViewController.php | 8 +- .../Controllers/Admin/PartnerController.php | 10 ++ .../Controllers/Admin/PaymentController.php | 4 + .../Controllers/Admin/ProductController.php | 17 +++ app/Http/Controllers/Admin/RoleController.php | 12 ++ .../Controllers/Admin/ServerController.php | 21 +++- .../Admin/ShopProductController.php | 15 ++- .../TicketCategoryController.php | 21 +++- .../TicketsController.php | 42 +++++-- .../Admin/UsefulLinkController.php | 7 ++ app/Http/Controllers/Admin/UserController.php | 58 ++++++++- .../Controllers/Admin/VoucherController.php | 9 ++ config/permissions_web.php | 21 +++- routes/web.php | 33 +++-- .../BlueInfinity/views/layouts/main.blade.php | 8 +- .../views/admin/overview/index.blade.php | 2 +- themes/default/views/layouts/main.blade.php | 116 +++++++++++------- .../views/mail/ticket/admin/create.blade.php | 2 +- .../views/mail/ticket/admin/reply.blade.php | 2 +- .../moderator/ticket/blacklist.blade.php | 6 +- .../views/moderator/ticket/category.blade.php | 8 +- .../views/moderator/ticket/index.blade.php | 6 +- .../views/moderator/ticket/show.blade.php | 8 +- 26 files changed, 342 insertions(+), 113 deletions(-) rename app/Http/Controllers/{Moderation => Admin}/TicketCategoryController.php (81%) rename app/Http/Controllers/{Moderation => Admin}/TicketsController.php (86%) diff --git a/app/Http/Controllers/Admin/ActivityLogController.php b/app/Http/Controllers/Admin/ActivityLogController.php index 2b0610948..25978a378 100644 --- a/app/Http/Controllers/Admin/ActivityLogController.php +++ b/app/Http/Controllers/Admin/ActivityLogController.php @@ -14,6 +14,7 @@ class ActivityLogController extends Controller { + const VIEW_PERMISSION = "admin.logs.read"; /** * Display a listing of the resource. * @@ -21,6 +22,9 @@ class ActivityLogController extends Controller */ public function index(Request $request) { + $this->checkPermission(self::VIEW_PERMISSION); + + $cronLogs = Storage::disk('logs')->exists('cron.log') ? Storage::disk('logs')->get('cron.log') : null; if ($request->input('search')) { diff --git a/app/Http/Controllers/Admin/ApplicationApiController.php b/app/Http/Controllers/Admin/ApplicationApiController.php index f037efe89..f6c00bbd8 100644 --- a/app/Http/Controllers/Admin/ApplicationApiController.php +++ b/app/Http/Controllers/Admin/ApplicationApiController.php @@ -16,6 +16,8 @@ class ApplicationApiController extends Controller { + const READ_PERMISSION = "admin.api.read"; + const WRITE_PERMISSION = "admin.api.write"; /** * Display a listing of the resource. * @@ -23,6 +25,8 @@ class ApplicationApiController extends Controller */ public function index(LocaleSettings $locale_settings) { + $this->checkPermission(self::READ_PERMISSION); + return view('admin.api.index', [ 'locale_datatables' => $locale_settings->datatables ]); @@ -35,6 +39,8 @@ public function index(LocaleSettings $locale_settings) */ public function create() { + $this->checkPermission(self::WRITE_PERMISSION); + return view('admin.api.create'); } @@ -76,6 +82,7 @@ public function show(ApplicationApi $applicationApi) */ public function edit(ApplicationApi $applicationApi) { + $this->checkPermission(self::WRITE_PERMISSION); return view('admin.api.edit', [ 'applicationApi' => $applicationApi, ]); @@ -107,6 +114,8 @@ public function update(Request $request, ApplicationApi $applicationApi) */ public function destroy(ApplicationApi $applicationApi) { + $this->checkPermission(self::WRITE_PERMISSION); + $applicationApi->delete(); return redirect()->back()->with('success', __('api key has been removed!')); diff --git a/app/Http/Controllers/Admin/LegalController.php b/app/Http/Controllers/Admin/LegalController.php index 0eafca573..27c858fba 100644 --- a/app/Http/Controllers/Admin/LegalController.php +++ b/app/Http/Controllers/Admin/LegalController.php @@ -10,6 +10,8 @@ class LegalController extends Controller { + const READ_PERMISSION = "admin.legal.read"; + const WRITE_PERMISSION = "admin.legal.write"; /** * Display * @@ -17,6 +19,8 @@ class LegalController extends Controller */ public function index() { + $this->checkPermission(self::READ_PERMISSION); + $tos = File::get(Theme::path($path = 'views', "default") . '/information/tos-content.blade.php'); $privacy = File::get(Theme::path($path = 'views', "default") . '/information/privacy-content.blade.php'); $imprint = File::get(Theme::path($path = 'views', "default") . '/information/imprint-content.blade.php'); @@ -29,6 +33,8 @@ public function index() } public function update(Request $request){ + $this->checkPermission(self::READ_PERMISSION); + $tos = $request->tos; $privacy = $request->privacy; $imprint = $request->imprint; diff --git a/app/Http/Controllers/Admin/OverViewController.php b/app/Http/Controllers/Admin/OverViewController.php index eac33d02c..90cd9307a 100644 --- a/app/Http/Controllers/Admin/OverViewController.php +++ b/app/Http/Controllers/Admin/OverViewController.php @@ -19,6 +19,8 @@ class OverViewController extends Controller { + const READ_PERMISSION = "admin.overview.read"; + const SYNC_PERMISSION = "admin.overview.sync"; public const TTL = 86400; private $pterodactyl; @@ -27,9 +29,11 @@ public function __construct(PterodactylSettings $ptero_settings) { $this->pterodactyl = new PterodactylClient($ptero_settings); } - + public function index(GeneralSettings $general_settings) { + $this->checkPermission(self::READ_PERMISSION); + //Get counters $counters = collect(); //Set basic variables in the collection @@ -225,6 +229,8 @@ public function index(GeneralSettings $general_settings) */ public function syncPterodactyl() { + $this->checkPermission(self::SYNC_PERMISSION); + Node::syncNodes(); Egg::syncEggs(); diff --git a/app/Http/Controllers/Admin/PartnerController.php b/app/Http/Controllers/Admin/PartnerController.php index cea2aec24..5c4a6babb 100644 --- a/app/Http/Controllers/Admin/PartnerController.php +++ b/app/Http/Controllers/Admin/PartnerController.php @@ -11,8 +11,12 @@ class PartnerController extends Controller { + const READ_PERMISSION = "admin.partners.read"; + const WRITE_PERMISSION = "admin.partners.write"; public function index(LocaleSettings $locale_settings) { + $this->checkPermission(self::READ_PERMISSION); + return view('admin.partners.index', [ 'locale_datatables' => $locale_settings->datatables ]); @@ -25,6 +29,8 @@ public function index(LocaleSettings $locale_settings) */ public function create() { + $this->checkPermission(self::WRITE_PERMISSION); + return view('admin.partners.create', [ 'partners' => PartnerDiscount::get(), 'users' => User::orderBy('name')->get(), @@ -62,6 +68,8 @@ public function store(Request $request) */ public function edit(PartnerDiscount $partner) { + $this->checkPermission(self::WRITE_PERMISSION); + return view('admin.partners.edit', [ 'partners' => PartnerDiscount::get(), 'partner' => $partner, @@ -98,6 +106,8 @@ public function update(Request $request, PartnerDiscount $partner) */ public function destroy(PartnerDiscount $partner) { + $this->checkPermission(self::WRITE_PERMISSION); + $partner->delete(); return redirect()->back()->with('success', __('partner has been removed!')); diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 02d91bde9..543cdcf69 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -24,11 +24,15 @@ class PaymentController extends Controller { const BUY_PERMISSION = 'user.shop.buy'; + const VIEW_PERMISSION = "admin.payments.read"; /** * @return Application|Factory|View */ public function index(LocaleSettings $locale_settings) { + $this->checkPermission(self::VIEW_PERMISSION); + + return view('admin.payments.index')->with([ 'payments' => Payment::paginate(15), 'locale_datatables' => $locale_settings->datatables diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index f26691e31..3dc95829b 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -19,6 +19,10 @@ class ProductController extends Controller { + const READ_PERMISSION = "admin.products.read"; + const WRITE_PERMISSION = "admin.products.write"; + const EDIT_PERMISSION = "admin.products.edit"; + const DELETE_PERMISSION = "admin.products.delete"; /** * Display a listing of the resource. * @@ -26,6 +30,8 @@ class ProductController extends Controller */ public function index(LocaleSettings $locale_settings) { + $this->checkPermission(self::READ_PERMISSION); + return view('admin.products.index', [ 'locale_datatables' => $locale_settings->datatables ]); @@ -38,6 +44,7 @@ public function index(LocaleSettings $locale_settings) */ public function create(GeneralSettings $general_settings) { + $this->checkPermission(self::WRITE_PERMISSION); return view('admin.products.create', [ 'locations' => Location::with('nodes')->get(), 'nests' => Nest::with('eggs')->get(), @@ -47,6 +54,8 @@ public function create(GeneralSettings $general_settings) public function clone(Product $product) { + $this->checkPermission(self::WRITE_PERMISSION); + return view('admin.products.create', [ 'product' => $product, 'locations' => Location::with('nodes')->get(), @@ -98,6 +107,8 @@ public function store(Request $request) */ public function show(Product $product, UserSettings $user_settings, GeneralSettings $general_settings) { + $this->checkPermission(self::READ_PERMISSION); + return view('admin.products.show', [ 'product' => $product, 'minimum_credits' => $user_settings->min_credits_to_make_server, @@ -113,6 +124,8 @@ public function show(Product $product, UserSettings $user_settings, GeneralSetti */ public function edit(Product $product, GeneralSettings $general_settings) { + $this->checkPermission(self::EDIT_PERMISSION); + return view('admin.products.edit', [ 'product' => $product, 'locations' => Location::with('nodes')->get(), @@ -167,6 +180,8 @@ public function update(Request $request, Product $product): RedirectResponse */ public function disable(Product $product) { + $this->checkPermission(self::WRITE_PERMISSION); + $product->update(['disabled' => ! $product->disabled]); return redirect()->route('admin.products.index')->with('success', 'Product has been updated!'); @@ -180,6 +195,8 @@ public function disable(Product $product) */ public function destroy(Product $product) { + $this->checkPermission(self::DELETE_PERMISSION); + $servers = $product->servers()->count(); if ($servers > 0) { return redirect()->back()->with('error', "Product cannot be removed while it's linked to {$servers} servers"); diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index ed4194bee..3850c1809 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -16,6 +16,10 @@ class RoleController extends Controller { + const READ_PERMISSION = "admin.roles.read"; + const CREATE_PERMISSION = "admin.roles.create"; + const EDIT_PERMISSION = "admin.roles.edit"; + const DELETE_PERMISSION = "admin.roles.delete"; /** * Display a listing of the resource. * @@ -26,6 +30,7 @@ class RoleController extends Controller public function index(Request $request) { + $this->checkPermission(self::READ_PERMISSION); //datatables if ($request->ajax()) { @@ -43,6 +48,7 @@ public function index(Request $request) */ public function create() { + $this->checkPermission(self::CREATE_PERMISSION); $permissions = Permission::all(); @@ -56,6 +62,8 @@ public function create() */ public function store(Request $request): RedirectResponse { + $this->checkPermission(self::CREATE_PERMISSION); + $role = Role::create([ 'name' => $request->name, 'color' => $request->color @@ -86,6 +94,7 @@ public function show() */ public function edit(Role $role) { + $this->checkPermission(self::EDIT_PERMISSION); $permissions = Permission::all(); @@ -100,6 +109,8 @@ public function edit(Role $role) */ public function update(Request $request, Role $role) { + $this->checkPermission(self::EDIT_PERMISSION); + if ($request->permissions) { if($role->id != 1){ //disable admin permissions change $role->syncPermissions($request->permissions); @@ -135,6 +146,7 @@ public function update(Request $request, Role $role) */ public function destroy(Role $role) { + $this->checkPermission(self::DELETE_PERMISSION); if($role->id == 1 || $role->id == 3 || $role->id == 4){ //cannot delete the hard coded roles return back()->with("error","You cannot delete that role"); diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php index 8e2475f5c..6fb3266a7 100644 --- a/app/Http/Controllers/Admin/ServerController.php +++ b/app/Http/Controllers/Admin/ServerController.php @@ -20,6 +20,13 @@ class ServerController extends Controller { + + const READ_PERMISSION = "admin.servers.read"; + const WRITE_PERMISSION = "admin.servers.write"; + const SUSPEND_PERMISSION = "admin.servers.suspend"; + const CHANGEOWNER_PERMISSION = "admin.servers.write.owner"; + const CHANGE_IDENTIFIER_PERMISSION ="admin.servers.write.identifier"; + const DELETE_PERMISSION = "admin.servers.delete"; private $pterodactyl; public function __construct(PterodactylSettings $ptero_settings) @@ -34,6 +41,8 @@ public function __construct(PterodactylSettings $ptero_settings) */ public function index(LocaleSettings $locale_settings) { + $this->checkPermission(self::READ_PERMISSION); + return view('admin.servers.index', [ 'locale_datatables' => $locale_settings->datatables ]); @@ -47,6 +56,8 @@ public function index(LocaleSettings $locale_settings) */ public function edit(Server $server) { + $this->checkPermission(self::WRITE_PERMISSION); + // get all users from the database $users = User::all(); @@ -70,7 +81,7 @@ public function update(Request $request, Server $server) ]); - if ($request->get('user_id') != $server->user_id) { + if ($request->get('user_id') != $server->user_id && $this->can(self::CHANGEOWNER_PERMISSION)) { // find the user $user = User::findOrFail($request->get('user_id')); @@ -89,7 +100,10 @@ public function update(Request $request, Server $server) } // update the identifier - $server->identifier = $request->get('identifier'); + if($this->can(self::CHANGE_IDENTIFIER_PERMISSION)) { + + $server->identifier = $request->get('identifier'); + } $server->save(); return redirect()->route('admin.servers.index')->with('success', 'Server updated!'); @@ -103,6 +117,7 @@ public function update(Request $request, Server $server) */ public function destroy(Server $server) { + $this->checkPermission(self::DELETE_PERMISSION); try { $server->delete(); @@ -118,6 +133,8 @@ public function destroy(Server $server) */ public function toggleSuspended(Server $server) { + $this->checkPermission(self::SUSPEND_PERMISSION); + try { $server->isSuspended() ? $server->unSuspend() : $server->suspend(); } catch (Exception $exception) { diff --git a/app/Http/Controllers/Admin/ShopProductController.php b/app/Http/Controllers/Admin/ShopProductController.php index 690493f8f..74c32639a 100644 --- a/app/Http/Controllers/Admin/ShopProductController.php +++ b/app/Http/Controllers/Admin/ShopProductController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin; +use App\Http\Controllers\Controller; use App\Models\ShopProduct; use App\Settings\GeneralSettings; use App\Settings\LocaleSettings; @@ -11,12 +12,15 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; -use Illuminate\Routing\Controller; use Illuminate\Validation\Rule; class ShopProductController extends Controller { + const READ_PERMISSION = 'admin.store.read'; + const WRITE_PERMISSION = 'admin.store.write'; + const DISABLE_PERMISSION = 'admin.store.disable'; + /** * Display a listing of the resource. * @@ -24,6 +28,8 @@ class ShopProductController extends Controller */ public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings) { + $this->checkPermission(self::READ_PERMISSION); + $isStoreEnabled = $general_settings->store_enabled; @@ -40,6 +46,8 @@ public function index(LocaleSettings $locale_settings, GeneralSettings $general_ */ public function create(GeneralSettings $general_settings) { + $this->checkPermission(self::WRITE_PERMISSION); + return view('admin.store.create', [ 'currencyCodes' => config('currency_codes'), 'credits_display_name' => $general_settings->credits_display_name @@ -78,6 +86,8 @@ public function store(Request $request) */ public function edit(ShopProduct $shopProduct, GeneralSettings $general_settings) { + $this->checkPermission(self::WRITE_PERMISSION); + return view('admin.store.edit', [ 'currencyCodes' => config('currency_codes'), 'shopProduct' => $shopProduct, @@ -117,6 +127,8 @@ public function update(Request $request, ShopProduct $shopProduct) */ public function disable(ShopProduct $shopProduct) { + $this->checkPermission(self::DISABLE_PERMISSION); + $shopProduct->update(['disabled' => !$shopProduct->disabled]); return redirect()->route('admin.store.index')->with('success', __('Product has been updated!')); @@ -130,6 +142,7 @@ public function disable(ShopProduct $shopProduct) */ public function destroy(ShopProduct $shopProduct) { + $this->checkPermission(self::WRITE_PERMISSION); $shopProduct->delete(); return redirect()->back()->with('success', __('Store item has been removed!')); diff --git a/app/Http/Controllers/Moderation/TicketCategoryController.php b/app/Http/Controllers/Admin/TicketCategoryController.php similarity index 81% rename from app/Http/Controllers/Moderation/TicketCategoryController.php rename to app/Http/Controllers/Admin/TicketCategoryController.php index 729e2f3c2..74fff87a7 100644 --- a/app/Http/Controllers/Moderation/TicketCategoryController.php +++ b/app/Http/Controllers/Admin/TicketCategoryController.php @@ -1,6 +1,6 @@ <?php -namespace App\Http\Controllers\Moderation; +namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\Ticket; @@ -9,15 +9,20 @@ class TicketCategoryController extends Controller { + const READ_PERMISSION = "admin.tickets.read"; + const WRITE_PERMISSION = "admin.tickets.write"; /** + * * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { + $this->checkPermission(self::READ_PERMISSION); + $categories = TicketCategory::all(); - return view('moderator.ticket.category')->with("categories",$categories); + return view('admin.ticket.category')->with("categories",$categories); } /** @@ -28,6 +33,8 @@ public function index() */ public function store(Request $request) { + $this->checkPermission(self::WRITE_PERMISSION); + $request->validate([ 'name' => 'required|string|max:191', ]); @@ -35,7 +42,7 @@ public function store(Request $request) TicketCategory::create($request->all()); - return redirect(route("moderator.ticket.category.index"))->with("success",__("Category created")); + return redirect(route("admin.ticket.category.index"))->with("success",__("Category created")); } /** @@ -46,6 +53,8 @@ public function store(Request $request) */ public function update(Request $request) { + $this->checkPermission(self::WRITE_PERMISSION); + $request->validate([ 'category' => 'required|int', 'name' => 'required|string|max:191', @@ -68,6 +77,8 @@ public function update(Request $request) */ public function destroy($id) { + $this->checkPermission(self::WRITE_PERMISSION); + $category = TicketCategory::where("id",$id)->firstOrFail(); if($category->id == 5 ){ //cannot delete "other" category @@ -84,7 +95,7 @@ public function destroy($id) $category->delete(); return redirect() - ->route('moderator.ticket.category.index') + ->route('admin.ticket.category.index') ->with('success', __('Category removed')); } @@ -101,7 +112,7 @@ public function datatable() }) ->addColumn('actions', function (TicketCategory $category) { return ' - <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('moderator.ticket.category.destroy', $category->id).'"> + <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('admin.ticket.category.destroy', $category->id).'"> '.csrf_field().' '.method_field('DELETE').' <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> diff --git a/app/Http/Controllers/Moderation/TicketsController.php b/app/Http/Controllers/Admin/TicketsController.php similarity index 86% rename from app/Http/Controllers/Moderation/TicketsController.php rename to app/Http/Controllers/Admin/TicketsController.php index cb05f36f8..3622c22ae 100644 --- a/app/Http/Controllers/Moderation/TicketsController.php +++ b/app/Http/Controllers/Admin/TicketsController.php @@ -1,8 +1,9 @@ <?php -namespace App\Http\Controllers\Moderation; +namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Http\Controllers\Moderation\Exception; use App\Models\Server; use App\Models\Ticket; use App\Models\TicketBlacklist; @@ -17,9 +18,16 @@ class TicketsController extends Controller { + const READ_PERMISSION = "admin.tickets.read"; + const WRITE_PERMISSION = "admin.tickets.write"; + + const BLACKLIST_READ_PERMISSION ='admin.ticket_blacklist.read'; + const BLACKLIST_WRITE_PERMISSION ='admin.ticket_blacklist.write'; public function index(LocaleSettings $locale_settings) { - return view('moderator.ticket.index', [ + $this->checkPermission(self::READ_PERMISSION); + + return view('admin.ticket.index', [ 'tickets' => Ticket::orderBy('id', 'desc')->paginate(10), 'ticketcategories' => TicketCategory::all(), 'locale_datatables' => $locale_settings->datatables @@ -28,6 +36,7 @@ public function index(LocaleSettings $locale_settings) public function show($ticket_id, PterodactylSettings $ptero_settings) { + $this->checkPermission(self::READ_PERMISSION); try { $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail(); } catch (Exception $e) @@ -39,11 +48,12 @@ public function show($ticket_id, PterodactylSettings $ptero_settings) $server = Server::where('id', $ticket->server)->first(); $pterodactyl_url = $ptero_settings->panel_url; - return view('moderator.ticket.show', compact('ticket', 'ticketcategory', 'ticketcomments', 'server', 'pterodactyl_url')); + return view('admin.ticket.show', compact('ticket', 'ticketcategory', 'ticketcomments', 'server', 'pterodactyl_url')); } public function changeStatus($ticket_id) { + $this->checkPermission(self::WRITE_PERMISSION); try { $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail(); } catch(Exception $e) @@ -65,6 +75,7 @@ public function changeStatus($ticket_id) public function delete($ticket_id) { + $this->checkPermission(self::WRITE_PERMISSION); try { $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail(); } catch (Exception $e) @@ -80,6 +91,9 @@ public function delete($ticket_id) public function reply(Request $request) { + $this->checkPermission(self::WRITE_PERMISSION); + + $this->validate($request, ['ticketcomment' => 'required']); try { $ticket = Ticket::where('id', $request->input('ticket_id'))->firstOrFail(); @@ -114,7 +128,7 @@ public function dataTable() return $tickets->ticketcategory->name; }) ->editColumn('title', function (Ticket $tickets) { - return '<a class="text-info" href="'.route('moderator.ticket.show', ['ticket_id' => $tickets->ticket_id]).'">'.'#'.$tickets->ticket_id.' - '.htmlspecialchars($tickets->title).'</a>'; + return '<a class="text-info" href="'.route('admin.ticket.show', ['ticket_id' => $tickets->ticket_id]).'">'.'#'.$tickets->ticket_id.' - '.htmlspecialchars($tickets->title).'</a>'; }) ->editColumn('user_id', function (Ticket $tickets) { return '<a href="'.route('admin.users.show', $tickets->user->id).'">'.$tickets->user->name.'</a>'; @@ -125,13 +139,13 @@ public function dataTable() $statusButtonText = ($tickets->status == "Closed") ? __('Reopen') : __('Close'); return ' - <a data-content="'.__('View').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('moderator.ticket.show', ['ticket_id' => $tickets->ticket_id]).'" class="btn btn-sm text-white btn-info mr-1"><i class="fas fa-eye"></i></a> - <form class="d-inline" method="post" action="'.route('moderator.ticket.changeStatus', ['ticket_id' => $tickets->ticket_id]).'"> + <a data-content="'.__('View').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.ticket.show', ['ticket_id' => $tickets->ticket_id]).'" class="btn btn-sm text-white btn-info mr-1"><i class="fas fa-eye"></i></a> + <form class="d-inline" method="post" action="'.route('admin.ticket.changeStatus', ['ticket_id' => $tickets->ticket_id]).'"> '.csrf_field().' '.method_field('POST').' <button data-content="'.__($statusButtonText).'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm text-white '.$statusButtonColor.' mr-1"><i class="fas '.$statusButtonIcon.'"></i></button> </form> - <form class="d-inline" method="post" action="'.route('moderator.ticket.delete', ['ticket_id' => $tickets->ticket_id]).'"> + <form class="d-inline" method="post" action="'.route('admin.ticket.delete', ['ticket_id' => $tickets->ticket_id]).'"> '.csrf_field().' '.method_field('POST').' <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm text-white btn-danger mr-1"><i class="fas fa-trash"></i></button> @@ -170,13 +184,17 @@ public function dataTable() public function blacklist(LocaleSettings $locale_settings) { - return view('moderator.ticket.blacklist', [ + $this->checkPermission(self::BLACKLIST_READ_PERMISSION); + + return view('admin.ticket.blacklist', [ 'locale_datatables' => $locale_settings->datatables ]); } public function blacklistAdd(Request $request) { + $this->checkPermission(self::BLACKLIST_WRITE_PERMISSION); + try { $user = User::where('id', $request->user_id)->firstOrFail(); $check = TicketBlacklist::where('user_id', $user->id)->first(); @@ -202,6 +220,8 @@ public function blacklistAdd(Request $request) public function blacklistDelete($id) { + $this->checkPermission(self::BLACKLIST_WRITE_PERMISSION); + $blacklist = TicketBlacklist::where('id', $id)->first(); $blacklist->delete(); @@ -210,6 +230,8 @@ public function blacklistDelete($id) public function blacklistChange($id) { + $this->checkPermission(self::BLACKLIST_WRITE_PERMISSION); + try { $blacklist = TicketBlacklist::where('id', $id)->first(); } @@ -254,12 +276,12 @@ public function dataTableBlacklist() }) ->addColumn('actions', function (TicketBlacklist $blacklist) { return ' - <form class="d-inline" method="post" action="'.route('moderator.ticket.blacklist.change', ['id' => $blacklist->id]).'"> + <form class="d-inline" method="post" action="'.route('admin.ticket.blacklist.change', ['id' => $blacklist->id]).'"> '.csrf_field().' '.method_field('POST').' <button data-content="'.__('Change Status').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm text-white btn-warning mr-1"><i class="fas fa-sync-alt"></i></button> </form> - <form class="d-inline" method="post" action="'.route('moderator.ticket.blacklist.delete', ['id' => $blacklist->id]).'"> + <form class="d-inline" method="post" action="'.route('admin.ticket.blacklist.delete', ['id' => $blacklist->id]).'"> '.csrf_field().' '.method_field('POST').' <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm text-white btn-danger mr-1"><i class="fas fa-trash"></i></button> diff --git a/app/Http/Controllers/Admin/UsefulLinkController.php b/app/Http/Controllers/Admin/UsefulLinkController.php index ddfdb7de6..17f8774b2 100644 --- a/app/Http/Controllers/Admin/UsefulLinkController.php +++ b/app/Http/Controllers/Admin/UsefulLinkController.php @@ -15,6 +15,8 @@ class UsefulLinkController extends Controller { + const READ_PERMISSION = "admin.useful_links.read"; + const WRITE_PERMISSION = "admin.useful_links.write"; /** * Display a listing of the resource. * @@ -22,6 +24,7 @@ class UsefulLinkController extends Controller */ public function index(LocaleSettings $locale_settings) { + $this->checkPermission(self::READ_PERMISSION); return view('admin.usefullinks.index', [ 'locale_datatables' => $locale_settings->datatables ]); @@ -34,6 +37,7 @@ public function index(LocaleSettings $locale_settings) */ public function create() { + $this->checkPermission(self::WRITE_PERMISSION); $positions = UsefulLinkLocation::cases(); return view('admin.usefullinks.create')->with('positions', $positions); } @@ -84,6 +88,8 @@ public function show(UsefulLink $usefullink) */ public function edit(UsefulLink $usefullink) { + $this->checkPermission(self::WRITE_PERMISSION); + $positions = UsefulLinkLocation::cases(); return view('admin.usefullinks.edit', [ 'link' => $usefullink, @@ -126,6 +132,7 @@ public function update(Request $request, UsefulLink $usefullink) */ public function destroy(UsefulLink $usefullink) { + $this->checkPermission(self::WRITE_PERMISSION); $usefullink->delete(); return redirect()->back()->with('success', __('product has been removed!')); diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index caf8fafd4..82d27c4e8 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -30,6 +30,20 @@ class UserController extends Controller { + const READ_PERMISSION = "admin.users.read"; + const WRITE_PERMISSION = "admin.users.write"; + const SUSPEND_PERMISSION = "admin.users.suspend"; + const CHANGE_EMAIL_PERMISSION = "admin.users.write.email"; + const CHANGE_CREDITS_PERMISSION = "admin.users.write.credits"; + const CHANGE_USERNAME_PERMISSION = "admin.users.write.username"; + const CHANGE_PASSWORD_PERMISSION = "admin.users.write.password"; + const CHANGE_ROLE_PERMISSION ="admin.users.write.role"; + const CHANGE_REFERAL_PERMISSION ="admin.users.write.referal"; + const CHANGE_PTERO_PERMISSION = "admin.users.write.pterodactyl"; + const DELETE_PERMISSION = "admin.users.delete"; + const NOTIFY_PERMISSION = "admin.users.notify"; + const LOGIN_PERMISSION = "admin.users.login_as"; + private $pterodactyl; public function __construct(PterodactylSettings $ptero_settings) @@ -45,6 +59,8 @@ public function __construct(PterodactylSettings $ptero_settings) */ public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings) { + $this->checkPermission(self::READ_PERMISSION); + return view('admin.users.index', [ 'locale_datatables' => $locale_settings->datatables, 'credits_display_name' => $general_settings->credits_display_name @@ -59,6 +75,8 @@ public function index(LocaleSettings $locale_settings, GeneralSettings $general_ */ public function show(User $user, LocaleSettings $locale_settings, GeneralSettings $general_settings) { + $this->checkPermission(self::READ_PERMISSION); + //QUERY ALL REFERRALS A USER HAS //i am not proud of this at all. $allReferals = []; @@ -109,6 +127,8 @@ public function json(Request $request) */ public function edit(User $user, GeneralSettings $general_settings) { + $this->checkPermission(self::WRITE_PERMISSION); + $roles = Role::all(); return view('admin.users.edit')->with([ 'user' => $user, @@ -134,12 +154,11 @@ public function update(Request $request, User $user) 'email' => 'required|string|email', 'credits' => 'required|numeric|min:0|max:99999999', 'server_limit' => 'required|numeric|min:0|max:1000000', - 'role' => Rule::in(['admin', 'moderator', 'client', 'member']), 'referral_code' => "required|string|min:2|max:32|unique:users,referral_code,{$user->id}", ]); //update roles - if ($request->roles) { + if ($request->roles && $this->can(self::CHANGE_ROLE_PERMISSION)) { $user->syncRoles($request->roles); } @@ -149,7 +168,7 @@ public function update(Request $request, User $user) ]); } - if (!is_null($request->input('new_password'))) { + if (!is_null($request->input('new_password')) && $this->can(self::CHANGE_PASSWORD_PERMISSION)) { $request->validate([ 'new_password' => 'required|string|min:8', 'new_password_confirmation' => 'required|same:new_password', @@ -160,7 +179,24 @@ public function update(Request $request, User $user) ]); } - $user->update($request->all()); + if($this->can(self::CHANGE_USERNAME_PERMISSION)){ + $user->name = $request->name; + } + if($this->can(self::CHANGE_CREDITS_PERMISSION)){ + $user->credits = $request->credits; + } + if($this->can(self::CHANGE_PTERO_PERMISSION)){ + $user->pterodactyl_id = $request->pterodactyl_id; + } + if($this->can(self::CHANGE_REFERAL_PERMISSION)){ + $user->referral_code = $request->referral_code; + } + if($this->can(self::CHANGE_EMAIL_PERMISSION)){ + $user->email = $request->email; + } + + $user->save(); + event(new UserUpdateCreditsEvent($user)); return redirect()->route('admin.users.index')->with('success', 'User updated!'); @@ -174,7 +210,9 @@ public function update(Request $request, User $user) */ public function destroy(User $user) { - if ($user->hasRole("Admin") && User::query()->where('role', 'admin')->count() === 1) { + $this->checkPermission(self::DELETE_PERMISSION); + + if ($user->hasRole(1) && User::role(1)->count() === 1) { return redirect()->back()->with('error', __('You can not delete the last admin!')); } @@ -203,6 +241,8 @@ public function verifyEmail(User $user) */ public function loginAs(Request $request, User $user) { + $this->checkPermission(self::LOGIN_PERMISSION); + $request->session()->put('previousUser', Auth::user()->id); Auth::login($user); @@ -215,6 +255,8 @@ public function loginAs(Request $request, User $user) */ public function logBackIn(Request $request) { + $this->checkPermission(self::LOGIN_PERMISSION); + Auth::loginUsingId($request->session()->get('previousUser'), true); $request->session()->remove('previousUser'); @@ -229,6 +271,8 @@ public function logBackIn(Request $request) */ public function notifications() { + $this->checkPermission(self::NOTIFY_PERMISSION); + return view('admin.users.notifications'); } @@ -243,6 +287,8 @@ public function notifications() */ public function notify(Request $request) { + $this->checkPermission(self::NOTIFY_PERMISSION); + $data = $request->validate([ 'via' => 'required|min:1|array', 'via.*' => 'required|string|in:mail,database', @@ -283,6 +329,8 @@ public function notify(Request $request) */ public function toggleSuspended(User $user) { + $this->checkPermission(self::SUSPEND_PERMISSION); + try { !$user->isSuspended() ? $user->suspend() : $user->unSuspend(); } catch (Exception $exception) { diff --git a/app/Http/Controllers/Admin/VoucherController.php b/app/Http/Controllers/Admin/VoucherController.php index 734981763..f39f9c335 100644 --- a/app/Http/Controllers/Admin/VoucherController.php +++ b/app/Http/Controllers/Admin/VoucherController.php @@ -19,6 +19,8 @@ class VoucherController extends Controller { + const READ_PERMISSION = "admin.voucher.read"; + const WRITE_PERMISSION = "admin.voucher.write"; /** * Display a listing of the resource. * @@ -26,6 +28,8 @@ class VoucherController extends Controller */ public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings) { + $this->checkPermission(self::READ_PERMISSION); + return view('admin.vouchers.index', [ 'locale_datatables' => $locale_settings->datatables, 'credits_display_name' => $general_settings->credits_display_name @@ -39,6 +43,7 @@ public function index(LocaleSettings $locale_settings, GeneralSettings $general_ */ public function create(GeneralSettings $general_settings) { + $this->checkPermission(self::WRITE_PERMISSION); return view('admin.vouchers.create', [ 'credits_display_name' => $general_settings->credits_display_name ]); @@ -84,6 +89,7 @@ public function show(Voucher $voucher) */ public function edit(Voucher $voucher, GeneralSettings $general_settings) { + $this->checkPermission(self::WRITE_PERMISSION); return view('admin.vouchers.edit', [ 'voucher' => $voucher, 'credits_display_name' => $general_settings->credits_display_name @@ -120,6 +126,7 @@ public function update(Request $request, Voucher $voucher) */ public function destroy(Voucher $voucher) { + $this->checkPermission(self::WRITE_PERMISSION); $voucher->delete(); return redirect()->back()->with('success', __('voucher has been removed!')); @@ -127,6 +134,8 @@ public function destroy(Voucher $voucher) public function users(Voucher $voucher, LocaleSettings $locale_settings, GeneralSettings $general_settings) { + $this->checkPermission(self::READ_PERMISSION); + return view('admin.vouchers.users', [ 'voucher' => $voucher, 'locale_datatables' => $locale_settings->datatables, diff --git a/config/permissions_web.php b/config/permissions_web.php index accbd07a0..ad6718427 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -6,13 +6,15 @@ /* * Permissions for admin */ - 'admin.sidebar.read', 'admin.roles.read', - 'admin.roles.write', + 'admin.roles.create', + 'admin.roles.edit', + 'admin.roles.delete', 'admin.ticket.read', + 'admin.tickets.write', 'admin.ticket_blacklist.read', 'admin.ticket_blacklist.write', @@ -32,13 +34,17 @@ 'admin.users.write.role', 'admin.users.write.referal', 'admin.users.write.pterodactyl', + 'admin.users.write.email', + 'admin.users.notify', + 'admin.users.login_as', + 'admin.users.delete', 'admin.servers.read', 'admin.servers.write', 'admin.servers.suspend', - 'admin.server.write.owner', - 'admin.server.write.identifier', - 'admin.server.delete', + 'admin.servers.write.owner', + 'admin.servers.write.identifier', + 'admin.servers.delete', 'admin.products.read', 'admin.products.create', @@ -58,6 +64,11 @@ 'admin.legal.read', 'admin.legal.write', + 'admin.payments.read', + + 'admin.partners.read', + 'admin.partners.write', + 'admin.logs.read', /* diff --git a/routes/web.php b/routes/web.php index c7c6a9de1..5f96806ae 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,13 +17,13 @@ use App\Http\Controllers\Admin\ServerController as AdminServerController; use App\Http\Controllers\Admin\SettingsController; use App\Http\Controllers\Admin\ShopProductController; +use App\Http\Controllers\Admin\TicketCategoryController; +use App\Http\Controllers\Admin\TicketsController as AdminTicketsController; use App\Http\Controllers\Admin\UsefulLinkController; use App\Http\Controllers\Admin\UserController; use App\Http\Controllers\Admin\VoucherController; use App\Http\Controllers\Auth\SocialiteController; use App\Http\Controllers\HomeController; -use App\Http\Controllers\Moderation\TicketCategoryController; -use App\Http\Controllers\Moderation\TicketsController as ModTicketsController; use App\Http\Controllers\NotificationController; use App\Http\Controllers\ProductController as FrontProductController; use App\Http\Controllers\ProfileController; @@ -117,7 +117,7 @@ //admin - Route::prefix('admin')->name('admin.')->middleware('admin')->group(function () { + Route::prefix('admin')->name('admin.')->group(function () { //Roles Route::get('roles/datatable', [RoleController::class, 'datatable'])->name('roles.datatable'); Route::resource('roles', RoleController::class); @@ -199,29 +199,28 @@ Route::resource('api', ApplicationApiController::class)->parameters([ 'api' => 'applicationApi', ]); - }); - //mod - Route::prefix('moderator')->name('moderator.')->middleware('moderator')->group(function () { //ticket moderation - Route::get('ticket', [ModTicketsController::class, 'index'])->name('ticket.index'); - Route::get('ticket/datatable', [ModTicketsController::class, 'datatable'])->name('ticket.datatable'); - Route::get('ticket/show/{ticket_id}', [ModTicketsController::class, 'show'])->name('ticket.show'); - Route::post('ticket/reply', [ModTicketsController::class, 'reply'])->name('ticket.reply'); - Route::post('ticket/status/{ticket_id}', [ModTicketsController::class, 'changeStatus'])->name('ticket.changeStatus'); - Route::post('ticket/delete/{ticket_id}', [ModTicketsController::class, 'delete'])->name('ticket.delete'); + Route::get('ticket', [AdminTicketsController::class, 'index'])->name('ticket.index'); + Route::get('ticket/datatable', [AdminTicketsController::class, 'datatable'])->name('ticket.datatable'); + Route::get('ticket/show/{ticket_id}', [AdminTicketsController::class, 'show'])->name('ticket.show'); + Route::post('ticket/reply', [AdminTicketsController::class, 'reply'])->name('ticket.reply'); + Route::post('ticket/status/{ticket_id}', [AdminTicketsController::class, 'changeStatus'])->name('ticket.changeStatus'); + Route::post('ticket/delete/{ticket_id}', [AdminTicketsController::class, 'delete'])->name('ticket.delete'); //ticket moderation blacklist - Route::get('ticket/blacklist', [ModTicketsController::class, 'blacklist'])->name('ticket.blacklist'); - Route::post('ticket/blacklist', [ModTicketsController::class, 'blacklistAdd'])->name('ticket.blacklist.add'); - Route::post('ticket/blacklist/delete/{id}', [ModTicketsController::class, 'blacklistDelete'])->name('ticket.blacklist.delete'); - Route::post('ticket/blacklist/change/{id}', [ModTicketsController::class, 'blacklistChange'])->name('ticket.blacklist.change'); - Route::get('ticket/blacklist/datatable', [ModTicketsController::class, 'dataTableBlacklist'])->name('ticket.blacklist.datatable'); + Route::get('ticket/blacklist', [AdminTicketsController::class, 'blacklist'])->name('ticket.blacklist'); + Route::post('ticket/blacklist', [AdminTicketsController::class, 'blacklistAdd'])->name('ticket.blacklist.add'); + Route::post('ticket/blacklist/delete/{id}', [AdminTicketsController::class, 'blacklistDelete'])->name('ticket.blacklist.delete'); + Route::post('ticket/blacklist/change/{id}', [AdminTicketsController::class, 'blacklistChange'])->name('ticket.blacklist.change'); + Route::get('ticket/blacklist/datatable', [AdminTicketsController::class, 'dataTableBlacklist'])->name('ticket.blacklist.datatable'); Route::get('ticket/category/datatable', [TicketCategoryController::class, 'datatable'])->name('ticket.category.datatable'); Route::resource("ticket/category", TicketCategoryController::class, ['as' => 'ticket']); }); + + Route::get('/home', [HomeController::class, 'index'])->name('home'); }); diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index a5c13de11..aa4a7cf55 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -257,15 +257,15 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> <li class="nav-header">{{ __('Moderation') }}</li> <li class="nav-item"> - <a href="{{ route('moderator.ticket.index') }}" - class="nav-link @if (Request::routeIs('moderator.ticket.index')) active @endif"> + <a href="{{ route('admin.ticket.index') }}" + class="nav-link @if (Request::routeIs('admin.ticket.index')) active @endif"> <i class="nav-icon fas fa-ticket-alt"></i> <p>{{ __('Ticket List') }}</p> </a> </li> <li class="nav-item"> - <a href="{{ route('moderator.ticket.blacklist') }}" - class="nav-link @if (Request::routeIs('moderator.ticket.blacklist')) active @endif"> + <a href="{{ route('admin.ticket.blacklist') }}" + class="nav-link @if (Request::routeIs('admin.ticket.blacklist')) active @endif"> <i class="nav-icon fas fa-user-times"></i> <p>{{ __('Ticket Blacklist') }}</p> </a> diff --git a/themes/default/views/admin/overview/index.blade.php b/themes/default/views/admin/overview/index.blade.php index d5b8d5a8e..1e54c6f6a 100644 --- a/themes/default/views/admin/overview/index.blade.php +++ b/themes/default/views/admin/overview/index.blade.php @@ -183,7 +183,7 @@ class="fas fa-sync mr-2"></i>{{__('Sync')}}</a> @foreach($tickets as $ticket_id => $ticket) <tr> - <td><a class="text-info" href="{{route('moderator.ticket.show', ['ticket_id' => $ticket_id])}}">#{{$ticket_id}} - {{$ticket->title}}</td> + <td><a class="text-info" href="{{route('admin.ticket.show', ['ticket_id' => $ticket_id])}}">#{{$ticket_id}} - {{$ticket->title}}</td> <td><a href="{{route('admin.users.show', $ticket->user_id)}}">{{$ticket->user}}</a></td> <td><span class="badge {{$ticket->statusBadgeColor}}">{{$ticket->status}}</span></td> <td>{{$ticket->last_updated}}</td> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index c72187bd9..5c0f2f999 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -257,28 +257,11 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> @endcanany @endif - @if ((Auth::user()->hasRole(1) || Auth::user()->role == 'moderator') && $ticket_enabled) - <li class="nav-header">{{ __('Moderation') }}</li> - <li class="nav-item"> - <a href="{{ route('moderator.ticket.index') }}" - class="nav-link @if (Request::routeIs('moderator.ticket.index')) active @endif"> - <i class="nav-icon fas fa-ticket-alt"></i> - <p>{{ __('Ticket List') }}</p> - </a> - </li> - <li class="nav-item"> - <a href="{{ route('moderator.ticket.blacklist') }}" - class="nav-link @if (Request::routeIs('moderator.ticket.blacklist')) active @endif"> - <i class="nav-icon fas fa-user-times"></i> - <p>{{ __('Ticket Blacklist') }}</p> - </a> - </li> - @endif - @if (Auth::user()->hasRole(1)) <li class="nav-header">{{ __('Administration') }}</li> + @canany(['admin.overview.read','admin.overview.sync']) <li class="nav-item"> <a href="{{ route('admin.overview.index') }}" class="nav-link @if (Request::routeIs('admin.overview.*')) active @endif"> @@ -286,7 +269,29 @@ class="nav-link @if (Request::routeIs('admin.overview.*')) active @endif"> <p>{{ __('Overview') }}</p> </a> </li> + @endcanany + + @canany(['admin.ticket.read','admin.tickets.write']) + <li class="nav-item"> + <a href="{{ route('admin.ticket.index') }}" + class="nav-link @if (Request::routeIs('admin.ticket.index')) active @endif"> + <i class="nav-icon fas fa-ticket-alt"></i> + <p>{{ __('Ticket List') }}</p> + </a> + </li> + @endcanany + + @canany(['admin.ticket_blacklist.read','admin.ticket_blacklist.write']) + <li class="nav-item"> + <a href="{{ route('admin.ticket.blacklist') }}" + class="nav-link @if (Request::routeIs('admin.ticket.blacklist')) active @endif"> + <i class="nav-icon fas fa-user-times"></i> + <p>{{ __('Ticket Blacklist') }}</p> + </a> + </li> + @endcanany + @canany(['admin.roles.read','admin.roles.write']) <li class="nav-item"> <a href="{{ route('admin.roles.index') }}" class="nav-link @if (Request::routeIs('admin.roles.*')) active @endif"> @@ -294,6 +299,7 @@ class="nav-link @if (Request::routeIs('admin.roles.*')) active @endif"> <p>{{ __('Role Management') }}</p> </a> </li> + @endcanany <li class="nav-item"> <a href="{{ route('admin.settings.index') }}" @@ -303,6 +309,7 @@ class="nav-link @if (Request::routeIs('admin.settings.*')) active @endif"> </a> </li> + @canany(['admin.api.read','admin.api.write']) <li class="nav-item"> <a href="{{ route('admin.api.index') }}" class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> @@ -310,9 +317,18 @@ class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> <p>{{ __('Application API') }}</p> </a> </li> - + @endcanany <li class="nav-header">{{ __('Management') }}</li> + @canany(['admin.users.read', + 'admin.users.write', + 'admin.users.suspend', + 'admin.users.write.credits', + 'admin.users.write.username', + 'admin.users.write.password', + 'admin.users.write.role', + 'admin.users.write.referal', + 'admin.users.write.pterodactyl']) <li class="nav-item"> <a href="{{ route('admin.users.index') }}" class="nav-link @if (Request::routeIs('admin.users.*')) active @endif"> @@ -320,7 +336,13 @@ class="nav-link @if (Request::routeIs('admin.users.*')) active @endif"> <p>{{ __('Users') }}</p> </a> </li> - + @endcanany + @canany(['admin.servers.read', + 'admin.servers.write', + 'admin.servers.suspend', + 'admin.servers.write.owner', + 'admin.servers.write.identifier', + 'admin.servers.delete']) <li class="nav-item"> <a href="{{ route('admin.servers.index') }}" class="nav-link @if (Request::routeIs('admin.servers.*')) active @endif"> @@ -328,7 +350,11 @@ class="nav-link @if (Request::routeIs('admin.servers.*')) active @endif"> <p>{{ __('Servers') }}</p> </a> </li> - + @endcanany + @canany(['admin.products.read', + 'admin.products.create', + 'admin.products.edit', + 'admin.products.delete',]) <li class="nav-item"> <a href="{{ route('admin.products.index') }}" class="nav-link @if (Request::routeIs('admin.products.*')) active @endif"> @@ -336,7 +362,8 @@ class="nav-link @if (Request::routeIs('admin.products.*')) active @endif"> <p>{{ __('Products') }}</p> </a> </li> - + @endcanany + @canany(['admin.store.read','admin.store.write','admin.store.disable']) <li class="nav-item"> <a href="{{ route('admin.store.index') }}" class="nav-link @if (Request::routeIs('admin.store.*')) active @endif"> @@ -344,7 +371,8 @@ class="nav-link @if (Request::routeIs('admin.store.*')) active @endif"> <p>{{ __('Store') }}</p> </a> </li> - + @endcanany + @canany(["admin.voucher.read","admin.voucher.read"]) <li class="nav-item"> <a href="{{ route('admin.vouchers.index') }}" class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif"> @@ -352,7 +380,8 @@ class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif"> <p>{{ __('Vouchers') }}</p> </a> </li> - + @endcanany + @canany(["admin.partners.read","admin.partners.read"]) <li class="nav-item"> <a href="{{ route('admin.partners.index') }}" class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif"> @@ -360,28 +389,13 @@ class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif"> <p>{{ __('Partners') }}</p> </a> </li> + @endcanany - {{-- <li class="nav-header">Pterodactyl</li> --}} - - {{-- <li class="nav-item"> --}} - {{-- <a href="{{route('admin.nodes.index')}}" --}} - {{-- class="nav-link @if (Request::routeIs('admin.nodes.*')) active @endif"> --}} - {{-- <i class="nav-icon fas fa-sitemap"></i> --}} - {{-- <p>Nodes</p> --}} - {{-- </a> --}} - {{-- </li> --}} - - {{-- <li class="nav-item"> --}} - {{-- <a href="{{route('admin.nests.index')}}" --}} - {{-- class="nav-link @if (Request::routeIs('admin.nests.*')) active @endif"> --}} - {{-- <i class="nav-icon fas fa-th-large"></i> --}} - {{-- <p>Nests</p> --}} - {{-- </a> --}} - {{-- </li> --}} - - - <li class="nav-header">{{ __('Other') }}</li> + @canany(["admin.useful_links.read","admin.legal.read"]) + <li class="nav-header">{{ __('Other') }}</li> + @endcanany + @canany(["admin.useful_links.read","admin.useful_links.write"]) <li class="nav-item"> <a href="{{ route('admin.usefullinks.index') }}" class="nav-link @if (Request::routeIs('admin.usefullinks.*')) active @endif"> @@ -389,7 +403,9 @@ class="nav-link @if (Request::routeIs('admin.usefullinks.*')) active @endif"> <p>{{ __('Useful Links') }}</p> </a> </li> + @endcanany + @canany(["admin.legal.read","admin.legal.write"]) <li class="nav-item"> <a href="{{ route('admin.legal.index') }}" class="nav-link @if (Request::routeIs('admin.legal.*')) active @endif"> @@ -397,9 +413,14 @@ class="nav-link @if (Request::routeIs('admin.legal.*')) active @endif"> <p>{{ __('Legal Sites') }}</p> </a> </li> + @endcanany + - <li class="nav-header">{{ __('Logs') }}</li> + @canany(["admin.payments.read","admin.logs.read"]) + <li class="nav-header">{{ __('Logs') }}</li> + @endcanany + @can("admin.payments.read") <li class="nav-item"> <a href="{{ route('admin.payments.index') }}" class="nav-link @if (Request::routeIs('admin.payments.*')) active @endif"> @@ -410,7 +431,9 @@ class="badge badge-success right">{{ \App\Models\Payment::count() }}</span> </p> </a> </li> + @endcan + @can("admin.logs.read") <li class="nav-item"> <a href="{{ route('admin.activitylogs.index') }}" class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> @@ -418,7 +441,8 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <p>{{ __('Activity Logs') }}</p> </a> </li> - @endif + @endcan + </ul> </nav> diff --git a/themes/default/views/mail/ticket/admin/create.blade.php b/themes/default/views/mail/ticket/admin/create.blade.php index b0b3f1a3f..4ee96a697 100644 --- a/themes/default/views/mail/ticket/admin/create.blade.php +++ b/themes/default/views/mail/ticket/admin/create.blade.php @@ -17,7 +17,7 @@ You can respond to this ticket by simply replying to this email or through the admin area at the url below. <br> -{{ route('moderator.ticket.show', ['ticket_id' => $ticket->ticket_id]) }} +{{ route('admin.ticket.show', ['ticket_id' => $ticket->ticket_id]) }} <br> {{__('Thanks')}},<br> diff --git a/themes/default/views/mail/ticket/admin/reply.blade.php b/themes/default/views/mail/ticket/admin/reply.blade.php index 704db26f5..a6f8cceb5 100644 --- a/themes/default/views/mail/ticket/admin/reply.blade.php +++ b/themes/default/views/mail/ticket/admin/reply.blade.php @@ -17,7 +17,7 @@ You can respond to this ticket by simply replying to this email or through the admin area at the url below. <br> -{{ route('moderator.ticket.show', ['ticket_id' => $ticket->ticket_id]) }} +{{ route('admin.ticket.show', ['ticket_id' => $ticket->ticket_id]) }} <br> {{__('Thanks')}},<br> diff --git a/themes/default/views/moderator/ticket/blacklist.blade.php b/themes/default/views/moderator/ticket/blacklist.blade.php index 1e91393ce..e26304a16 100644 --- a/themes/default/views/moderator/ticket/blacklist.blade.php +++ b/themes/default/views/moderator/ticket/blacklist.blade.php @@ -12,7 +12,7 @@ <ol class="breadcrumb float-sm-right"> <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li> <li class="breadcrumb-item"><a class="text-muted" - href="{{ route('moderator.ticket.blacklist') }}">{{ __('Ticket Blacklist') }}</a> + href="{{ route('admin.ticket.blacklist') }}">{{ __('Ticket Blacklist') }}</a> </li> </ol> </div> @@ -60,7 +60,7 @@ class="fas fa-info-circle"></i></h5> </div> <div class="card-body"> - <form action="{{route('moderator.ticket.blacklist.add')}}" method="POST" class="ticket-form"> + <form action="{{route('admin.ticket.blacklist.add')}}" method="POST" class="ticket-form"> @csrf <div class="custom-control mb-3 p-0"> <label for="user_id">{{ __('User') }}: @@ -95,7 +95,7 @@ class="fas fa-info-circle"></i></h5> processing: true, serverSide: true, stateSave: true, - ajax: "{{route('moderator.ticket.blacklist.datatable')}}", + ajax: "{{route('admin.ticket.blacklist.datatable')}}", columns: [ {data: 'user' , name : 'user.name'}, {data: 'status'}, diff --git a/themes/default/views/moderator/ticket/category.blade.php b/themes/default/views/moderator/ticket/category.blade.php index 28f723589..42524285e 100644 --- a/themes/default/views/moderator/ticket/category.blade.php +++ b/themes/default/views/moderator/ticket/category.blade.php @@ -12,7 +12,7 @@ <ol class="breadcrumb float-sm-right"> <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li> <li class="breadcrumb-item"><a class="text-muted" - href="{{ route("moderator.ticket.category.index") }}">{{ __('Ticket Categories') }}</a> + href="{{ route("admin.ticket.category.index") }}">{{ __('Ticket Categories') }}</a> </li> </ol> </div> @@ -56,7 +56,7 @@ <h5 class="card-title">{{__('Add Category')}} </div> <div class="card-body"> - <form action="{{route("moderator.ticket.category.store")}}" method="POST" class="ticket-form"> + <form action="{{route("admin.ticket.category.store")}}" method="POST" class="ticket-form"> @csrf <div class="form-group "> <label for="name" class="control-label">{{__("Name")}}</label> @@ -73,7 +73,7 @@ <h5 class="card-title">{{__('Edit Category')}} </div> <div class="card-body"> - <form action="{{route("moderator.ticket.category.update","1")}}" method="POST" class="ticket-form"> + <form action="{{route("admin.ticket.category.update","1")}}" method="POST" class="ticket-form"> @csrf @method('PATCH') <select id="category" style="width:100%" class="custom-select" name="category" @@ -109,7 +109,7 @@ processing: true, serverSide: true, stateSave: true, - ajax: "{{route('moderator.ticket.category.datatable')}}", + ajax: "{{route('admin.ticket.category.datatable')}}", columns: [ {data: 'id'}, {data: 'name'}, diff --git a/themes/default/views/moderator/ticket/index.blade.php b/themes/default/views/moderator/ticket/index.blade.php index f765b836c..5dc0bb936 100644 --- a/themes/default/views/moderator/ticket/index.blade.php +++ b/themes/default/views/moderator/ticket/index.blade.php @@ -12,7 +12,7 @@ <ol class="breadcrumb float-sm-right"> <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> <li class="breadcrumb-item"><a class="text-muted" - href="{{route('moderator.ticket.index')}}">{{__('Ticket List')}}</a></li> + href="{{route('admin.ticket.index')}}">{{__('Ticket List')}}</a></li> </ol> </div> </div> @@ -30,7 +30,7 @@ <div class="d-flex justify-content-between"> <h5 class="card-title"><i class="fas fa-ticket-alt mr-2"></i>{{__('Ticket List')}}</h5> </div> - <a href="{{route("moderator.ticket.category.index")}}"><button class="btn btn-primary float-right">+ {{__("Add Category")}}</button></a> + <a href="{{route("admin.ticket.category.index")}}"><button class="btn btn-primary float-right">+ {{__("Add Category")}}</button></a> </div> @@ -72,7 +72,7 @@ processing: true, serverSide: true, stateSave: true, - ajax: "{{route('moderator.ticket.datatable')}}", + ajax: "{{route('admin.ticket.datatable')}}", order: [[ 4, "desc" ]], columns: [ {data: 'category'}, diff --git a/themes/default/views/moderator/ticket/show.blade.php b/themes/default/views/moderator/ticket/show.blade.php index 3bec1feba..c2c67def6 100644 --- a/themes/default/views/moderator/ticket/show.blade.php +++ b/themes/default/views/moderator/ticket/show.blade.php @@ -12,7 +12,7 @@ <ol class="breadcrumb float-sm-right"> <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li> <li class="breadcrumb-item"><a class="text-muted" - href="{{ route('moderator.ticket.index') }}">{{ __('Ticket') }}</a> + href="{{ route('admin.ticket.index') }}">{{ __('Ticket') }}</a> </li> </ol> </div> @@ -74,7 +74,7 @@ <p><b>{{__("Created on")}}:</b> {{ $ticket->created_at->diffForHumans() }}</p> @if($ticket->status=='Closed') <form class="d-inline" method="post" - action="{{route('moderator.ticket.changeStatus', ['ticket_id' => $ticket->ticket_id ])}}"> + action="{{route('admin.ticket.changeStatus', ['ticket_id' => $ticket->ticket_id ])}}"> {{csrf_field()}} {{method_field("POST") }} <button data-content="{{__("Reopen")}}" data-toggle="popover" @@ -84,7 +84,7 @@ class="fas fa-redo"></i>{{__("Reopen")}}</button> </form> @else <form class="d-inline" method="post" - action="{{route('moderator.ticket.changeStatus', ['ticket_id' => $ticket->ticket_id ])}}"> + action="{{route('admin.ticket.changeStatus', ['ticket_id' => $ticket->ticket_id ])}}"> {{csrf_field()}} {{method_field("POST") }} <button data-content="{{__("Close")}}" data-toggle="popover" @@ -140,7 +140,7 @@ class="user-image" alt="User Image"> </div> @endforeach <div class="comment-form"> - <form action="{{ route('moderator.ticket.reply')}}" method="POST" class="form"> + <form action="{{ route('admin.ticket.reply')}}" method="POST" class="form"> {!! csrf_field() !!} <input type="hidden" name="ticket_id" value="{{ $ticket->id }}"> <div class="form-group{{ $errors->has('ticketcomment') ? ' has-error' : '' }}"> From 79432fce8214321c270ea74328ef9dfb49370275 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 01:21:19 +0200 Subject: [PATCH 121/514] Permission on sidebar --- app/Http/Controllers/Admin/UserController.php | 1 - themes/default/views/layouts/main.blade.php | 29 +++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 82d27c4e8..2edc3b7e5 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -255,7 +255,6 @@ public function loginAs(Request $request, User $user) */ public function logBackIn(Request $request) { - $this->checkPermission(self::LOGIN_PERMISSION); Auth::loginUsingId($request->session()->get('previousUser'), true); $request->session()->remove('previousUser'); diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 5c0f2f999..bc2da4ab3 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -257,9 +257,10 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> @endcanany @endif - - + <!-- lol how do i make this shorter? --> + @canany(['admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) <li class="nav-header">{{ __('Administration') }}</li> + @endcanany @canany(['admin.overview.read','admin.overview.sync']) <li class="nav-item"> @@ -318,7 +319,29 @@ class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> </a> </li> @endcanany + + <!-- good fuck do i shorten this lol --> + @canany(['admin.users.read', + 'admin.users.write', + 'admin.users.suspend', + 'admin.users.write.credits', + 'admin.users.write.username', + 'admin.users.write.password', + 'admin.users.write.role', + 'admin.users.write.referal', + 'admin.users.write.pterodactyl','admin.servers.read', + 'admin.servers.write', + 'admin.servers.suspend', + 'admin.servers.write.owner', + 'admin.servers.write.identifier', + 'admin.servers.delete','admin.products.read', + 'admin.products.create', + 'admin.products.edit', + 'admin.products.delete',]) <li class="nav-header">{{ __('Management') }}</li> + @endcanany + + @canany(['admin.users.read', 'admin.users.write', @@ -354,7 +377,7 @@ class="nav-link @if (Request::routeIs('admin.servers.*')) active @endif"> @canany(['admin.products.read', 'admin.products.create', 'admin.products.edit', - 'admin.products.delete',]) + 'admin.products.delete']) <li class="nav-item"> <a href="{{ route('admin.products.index') }}" class="nav-link @if (Request::routeIs('admin.products.*')) active @endif"> From 03eef835f7a3cd405e1d2c000c3fa70f60055b7c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 01:28:56 +0200 Subject: [PATCH 122/514] simple settings perms --- .../Controllers/Admin/SettingsController.php | 7 ++++++ config/permissions_web.php | 22 ++----------------- themes/default/views/layouts/main.blade.php | 4 +++- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 7e1c5457f..3c6782f08 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -15,6 +15,9 @@ class SettingsController extends Controller { + + const READ_PERMISSIONS = "admin.settings.read"; + const WRITE_PERMISSIONS = "admin.settings.write"; /** * Display a listing of the resource. * @@ -23,6 +26,8 @@ class SettingsController extends Controller public function index() { + $this->checkPermission(self::READ_PERMISSIONS); + // get all other settings in app/Settings directory // group items by file name like $categories $settings = collect(); @@ -91,6 +96,8 @@ public function index() */ public function update(Request $request) { + $this->checkPermission(self::WRITE_PERMISSIONS); + $category = request()->get('category'); $settings_class = request()->get('settings_class'); diff --git a/config/permissions_web.php b/config/permissions_web.php index ad6718427..47ba6a469 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -71,26 +71,8 @@ 'admin.logs.read', - /* - * Permissions for settings - */ - 'settings.sidebar.read', - - 'settings.invoices.read', - 'settings.invoices.write', - - 'settings.language.read', - 'settings.language.write', - - 'settings.misc.read', - 'settings.misc.write', - - 'settings.payment.read', - 'settings.payment.write', - - 'settings.system.read', - 'settings.system.write', - + 'admin.settings.read', + 'admin.settings.write', /* * Permissions for users */ diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index bc2da4ab3..49fc7332a 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -258,7 +258,7 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> @endif <!-- lol how do i make this shorter? --> - @canany(['admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) + @canany(['admin.settings.read','admin.settings.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) <li class="nav-header">{{ __('Administration') }}</li> @endcanany @@ -302,6 +302,7 @@ class="nav-link @if (Request::routeIs('admin.roles.*')) active @endif"> </li> @endcanany + @canany(['admin.settings.read','admin.settings.write']) <li class="nav-item"> <a href="{{ route('admin.settings.index') }}" class="nav-link @if (Request::routeIs('admin.settings.*')) active @endif"> @@ -309,6 +310,7 @@ class="nav-link @if (Request::routeIs('admin.settings.*')) active @endif"> <p>{{ __('Settings') }}</p> </a> </li> + @endcanany @canany(['admin.api.read','admin.api.write']) <li class="nav-item"> From a573454810f4775af4c3fd5db51c6ba239b8b243 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 10:59:15 +0200 Subject: [PATCH 123/514] full settings permissions --- .../Controllers/Admin/SettingsController.php | 9 ++-- config/permissions_web.php | 48 ++++++++++++++++++- .../views/admin/settings/index.blade.php | 5 ++ themes/default/views/layouts/main.blade.php | 31 +++++++++++- 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 3c6782f08..5b05db2b5 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -16,8 +16,7 @@ class SettingsController extends Controller { - const READ_PERMISSIONS = "admin.settings.read"; - const WRITE_PERMISSIONS = "admin.settings.write"; + /** * Display a listing of the resource. * @@ -26,7 +25,6 @@ class SettingsController extends Controller public function index() { - $this->checkPermission(self::READ_PERMISSIONS); // get all other settings in app/Settings directory // group items by file name like $categories @@ -96,9 +94,10 @@ public function index() */ public function update(Request $request) { - $this->checkPermission(self::WRITE_PERMISSIONS); - $category = request()->get('category'); + + $this->checkPermission("settings.".strtolower($category).".write"); + $settings_class = request()->get('settings_class'); if (method_exists($settings_class, 'getValidations')) { diff --git a/config/permissions_web.php b/config/permissions_web.php index 47ba6a469..0de102718 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -71,8 +71,52 @@ 'admin.logs.read', - 'admin.settings.read', - 'admin.settings.write', + /* + * Settings Permissions + */ + 'settings.discord.read', + 'settings.discord.write', + + 'settings.general.read', + 'settings.general.write', + + 'settings.invoice.read', + 'settings.invoice.write', + + 'settings.locale.read', + 'settings.locale.write', + + 'settings.mail.read', + 'settings.mail.write', + + 'settings.pterodactyl.read', + 'settings.pterodactyl.write', + + 'settings.referral.read', + 'settings.referral.write', + + 'settings.server.read', + 'settings.server.write', + + 'settings.ticket.read', + 'settings.ticket.write', + + 'settings.user.read', + 'settings.user.write', + + 'settings.website.read', + 'settings.website.write', + + 'settings.paypal.read', + 'settings.paypal.write', + + 'settings.stripe.read', + 'settings.stripe.write', + + 'settings.mollie.read', + 'settings.mollie.write', + + /* * Permissions for users */ diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 134e0a959..2c63d4280 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -45,6 +45,7 @@ <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" data-accordion="false"> @foreach ($settings as $category => $options) + @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) <li class="nav-item border-bottom-0"> <a href="#{{ $category }}" class="nav-link {{ $loop->first ? 'active' : '' }}" data-toggle="pill" @@ -56,6 +57,7 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> </p> </a> </li> + @endcanany @endforeach </ul> </nav> @@ -65,6 +67,7 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> <div class="col-10 p-0"> <div class="tab-content ml-3" style="width: 100%;"> @foreach ($settings as $category => $options) + @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) <div container class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" id="{{ $category }}" role="tabpanel"> @@ -158,6 +161,7 @@ class="custom-select w-100" name="{{ $key }}" </div> </div> + @endforeach <!-- TODO: Display this only on the General tab @@ -195,6 +199,7 @@ class="btn btn-secondary float-right ml-2">Reset</button> </div> </form> </div> + @endcanany @endforeach </div> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 49fc7332a..d72bf6ae8 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -258,7 +258,7 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> @endif <!-- lol how do i make this shorter? --> - @canany(['admin.settings.read','admin.settings.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) + @canany(['settings.discord.read','settings.discord.write','settings.general.read','settings.general.write','settings.invoice.read','settings.invoice.write','settings.locale.read','settings.locale.write','settings.mail.read','settings.mail.write','settings.pterodactyl.read','settings.pterodactyl.write','settings.referral.read','settings.referral.write','settings.server.read','settings.server.write','settings.ticket.read','settings.ticket.write','settings.user.read','settings.user.write','settings.website.read','settings.website.write','settings.paypal.read','settings.paypal.write','settings.stripe.read','settings.stripe.write','settings.mollie.read','settings.mollie.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) <li class="nav-header">{{ __('Administration') }}</li> @endcanany @@ -302,7 +302,34 @@ class="nav-link @if (Request::routeIs('admin.roles.*')) active @endif"> </li> @endcanany - @canany(['admin.settings.read','admin.settings.write']) + @canany(['settings.discord.read', + 'settings.discord.write', + 'settings.general.read', + 'settings.general.write', + 'settings.invoice.read', + 'settings.invoice.write', + 'settings.locale.read', + 'settings.locale.write', + 'settings.mail.read', + 'settings.mail.write', + 'settings.pterodactyl.read', + 'settings.pterodactyl.write', + 'settings.referral.read', + 'settings.referral.write', + 'settings.server.read', + 'settings.server.write', + 'settings.ticket.read', + 'settings.ticket.write', + 'settings.user.read', + 'settings.user.write', + 'settings.website.read', + 'settings.website.write', + 'settings.paypal.read', + 'settings.paypal.write', + 'settings.stripe.read', + 'settings.stripe.write', + 'settings.mollie.read', + 'settings.mollie.write',]) <li class="nav-item"> <a href="{{ route('admin.settings.index') }}" class="nav-link @if (Request::routeIs('admin.settings.*')) active @endif"> From 7d974d34081c39b87c1c08dee8e89572e715b55f Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 11:17:40 +0200 Subject: [PATCH 124/514] role powers --- app/Http/Controllers/Admin/RoleController.php | 15 ++++++++- .../2023_05_05_090127_role_power.php | 32 +++++++++++++++++++ database/seeders/PermissionsSeeder.php | 8 ++--- .../default/views/admin/roles/edit.blade.php | 7 ++++ .../default/views/admin/roles/index.blade.php | 2 ++ 5 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 database/migrations/2023_05_05_090127_role_power.php diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index 3850c1809..9a521938a 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -10,6 +10,7 @@ use Illuminate\Contracts\View\View; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; @@ -66,7 +67,8 @@ public function store(Request $request): RedirectResponse $role = Role::create([ 'name' => $request->name, - 'color' => $request->color + 'color' => $request->color, + 'power' => $request->power ]); if ($request->permissions) { @@ -96,6 +98,10 @@ public function edit(Role $role) { $this->checkPermission(self::EDIT_PERMISSION); + if(Auth::user()->roles[0]->power < $role->power){ + return back()->with("error","You dont have enough Power to edit that Role"); + } + $permissions = Permission::all(); return view('admin.roles.edit', compact('role', 'permissions')); @@ -111,6 +117,10 @@ public function update(Request $request, Role $role) { $this->checkPermission(self::EDIT_PERMISSION); + if(Auth::user()->roles[0]->power < $role->power){ + return back()->with("error","You dont have enough Power to edit that Role"); + } + if ($request->permissions) { if($role->id != 1){ //disable admin permissions change $role->syncPermissions($request->permissions); @@ -201,6 +211,9 @@ class="fa fas fa-trash"></i></button> ->editColumn('permissionscount', function ($query){ return $query->permissions_count; }) + ->editColumn('power', function (Role $role){ + return $role->power; + }) ->rawColumns(['actions', 'name']) ->make(true); } diff --git a/database/migrations/2023_05_05_090127_role_power.php b/database/migrations/2023_05_05_090127_role_power.php new file mode 100644 index 000000000..9c8da51d0 --- /dev/null +++ b/database/migrations/2023_05_05_090127_role_power.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('roles', function (Blueprint $table) { + $table->integer('power')->after("color")->default(50); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('roles', function (Blueprint $table) { + $table->dropColumn('power'); + }); + } +}; diff --git a/database/seeders/PermissionsSeeder.php b/database/seeders/PermissionsSeeder.php index e688bdf07..e7738e704 100644 --- a/database/seeders/PermissionsSeeder.php +++ b/database/seeders/PermissionsSeeder.php @@ -66,10 +66,10 @@ public function createRoles() 'user.referral', ]; /** @var Role $adminRole */ - $adminRole = Role::updateOrCreate(["name"=>"Admin","color"=>"#fa0000"]); - $supportRole = Role::updateOrCreate(["name"=>"Support-Team","color"=>"#00b0b3"]); - $clientRole = Role::updateOrCreate(["name"=>"Client","color"=>"#008009"]); - $userRole = Role::updateOrCreate(["name"=>"User","color"=>"#0052a3"]); + $adminRole = Role::updateOrCreate(["name"=>"Admin","color"=>"#fa0000", "power"=>100]); + $supportRole = Role::updateOrCreate(["name"=>"Support-Team","color"=>"#00b0b3","power"=>50]); + $clientRole = Role::updateOrCreate(["name"=>"Client","color"=>"#008009","power"=>10]); + $userRole = Role::updateOrCreate(["name"=>"User","color"=>"#0052a3","power"=>10]); $adminRole->givePermissionTo(Permission::findByName('*')); diff --git a/themes/default/views/admin/roles/edit.blade.php b/themes/default/views/admin/roles/edit.blade.php index 2abbe8087..453233f67 100644 --- a/themes/default/views/admin/roles/edit.blade.php +++ b/themes/default/views/admin/roles/edit.blade.php @@ -25,6 +25,13 @@ name="color" value="{{ isset($role) ? $role->color : null}}"/> + <x-input.number label="{{(__('Power'))}}" + name="power" + min="1" + max="100" + step="1" + value="{{ isset($role) ? $role->power : 10}}"/> + </div> <div class="col-lg-6"> diff --git a/themes/default/views/admin/roles/index.blade.php b/themes/default/views/admin/roles/index.blade.php index 0bc9d3a2e..213a7724d 100644 --- a/themes/default/views/admin/roles/index.blade.php +++ b/themes/default/views/admin/roles/index.blade.php @@ -22,6 +22,7 @@ class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> <th>{{__("Name")}}</th> <th>{{__("User count")}}</th> <th>{{__("Permissions count")}}</th> + <th>{{__("Power")}}</th> <th>{{__("Actions")}}</th> </tr> </thead> @@ -49,6 +50,7 @@ class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> {data: 'name'}, {data: 'usercount'}, {data: 'permissionscount'}, + {data: 'power'}, {data: 'actions' , sortable : false}, ], fnDrawCallback: function( oSettings ) { From e7f19af9f91898523f58a5e8ab8ac5133c2a1555 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 11:38:00 +0200 Subject: [PATCH 125/514] MOTD --- .../settings/2023_02_01_182158_create_website_settings.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/database/settings/2023_02_01_182158_create_website_settings.php b/database/settings/2023_02_01_182158_create_website_settings.php index c7e8fa515..fd542ff33 100644 --- a/database/settings/2023_02_01_182158_create_website_settings.php +++ b/database/settings/2023_02_01_182158_create_website_settings.php @@ -14,8 +14,10 @@ public function up(): void $this->migrator->add( 'website.motd_message', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:MOTD_MESSAGE") : - '<h1 style=\"text-align: center;\"><img style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https:\/\/ctrlpanel.gg\/img\/controlpanel.png\" alt=\"\" width=\"200\" height=\"200\"><span style=\"font-size: 36pt;\">Controlpanel.gg<\/span><\/h1>\r\n<p><span style=\"font-size: 18pt;\">Thank you for using our Software<\/span><\/p>\r\n<p><span style=\"font-size: 18pt;\">If you have any questions, make sure to join our <a href=\"https:\/\/discord.com\/invite\/4Y6HjD2uyU\" target=\"_blank\" rel=\"noopener\">Discord<\/a><\/span><\/p>\r\n<p><span style=\"font-size: 10pt;\">(you can change this message in the <a href=\"admin\/settings#system\">Settings<\/a> )<\/span><\/p>' - ); + "<h1 style='text-align: center;'><img style='display: block; margin-left: auto; margin-right: auto;' src='https://ctrlpanel.gg/img/controlpanel.png' alt=' width='200' height='200'><span style='font-size: 36pt;'>CtrlPanel.gg</span></h1> + <p><span style='font-size: 18pt;'>Thank you for using our Software</span></p> + <p><span style='font-size: 18pt;'>If you have any questions, make sure to join our <a href='https://discord.com/invite/4Y6HjD2uyU' target='_blank' rel='noopener'>Discord</a></span></p> + <p><span style='font-size: 10pt;'>(you can change this message in the <a href='admin/settings#system'>Settings</a> )</span></p>"); $this->migrator->add('website.show_imprint', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_IMPRINT") : false); $this->migrator->add('website.show_privacy', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_PRIVACY") : false); $this->migrator->add('website.show_tos', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_TOS") : false); From 57178da067ebc6827b0b08317cb3890aecfa7a95 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 11:54:12 +0200 Subject: [PATCH 126/514] Update app.php --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index 534fafe3c..9d82ff72b 100644 --- a/config/app.php +++ b/config/app.php @@ -4,7 +4,7 @@ return [ - 'version' => '0.9.4', + 'version' => '0.10', /* |-------------------------------------------------------------------------- From f71b6153e6a86b89a393ef6bfbef4ff93c3421dd Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 12:00:10 +0200 Subject: [PATCH 127/514] add back the github branch --- app/Providers/AppServiceProvider.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index aad430c63..4bd88d897 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -75,5 +75,19 @@ public function boot() $settings = $this->app->make(MailSettings::class); $settings->setConfig(); + + try { + $stringfromfile = file(base_path() . '/.git/HEAD'); + + $firstLine = $stringfromfile[0]; //get the string from the array + + $explodedstring = explode('/', $firstLine, 3); //seperate out by the "/" in the string + + $branchname = $explodedstring[2]; //get the one that is always the branch name + } catch (Exception $e) { + $branchname = 'unknown'; + Log::notice($e); + } + config(['BRANCHNAME' => $branchname]); } } From 44db7b4847290227cd2289c6697d19b7ff0faf6e Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 12:01:57 +0200 Subject: [PATCH 128/514] Update AppServiceProvider.php --- app/Providers/AppServiceProvider.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 4bd88d897..8b82efad9 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -60,6 +60,21 @@ public function boot() URL::forceScheme('https'); } + //get the Github Branch the panel is running on + try { + $stringfromfile = file(base_path() . '/.git/HEAD'); + + $firstLine = $stringfromfile[0]; //get the string from the array + + $explodedstring = explode('/', $firstLine, 3); //seperate out by the "/" in the string + + $branchname = $explodedstring[2]; //get the one that is always the branch name + } catch (Exception $e) { + $branchname = 'unknown'; + Log::notice($e); + } + config(['BRANCHNAME' => $branchname]); + // Do not run this code if no APP_KEY is set if (config('app.key') == null) return; @@ -76,18 +91,5 @@ public function boot() $settings = $this->app->make(MailSettings::class); $settings->setConfig(); - try { - $stringfromfile = file(base_path() . '/.git/HEAD'); - - $firstLine = $stringfromfile[0]; //get the string from the array - - $explodedstring = explode('/', $firstLine, 3); //seperate out by the "/" in the string - - $branchname = $explodedstring[2]; //get the one that is always the branch name - } catch (Exception $e) { - $branchname = 'unknown'; - Log::notice($e); - } - config(['BRANCHNAME' => $branchname]); } } From 0ca052a2ba94c0ed77694d66977aef0e35f2c8e5 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 12:19:33 +0200 Subject: [PATCH 129/514] remove global verification banner --- themes/default/views/layouts/main.blade.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index d72bf6ae8..bff2fe7f9 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -507,6 +507,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <div class="content-wrapper"> + <!-- @if (!Auth::user()->hasVerifiedEmail()) @if (Auth::user()->created_at->diffInHours(now(), false) > 1) <div class="alert alert-warning p-2 m-2"> @@ -518,6 +519,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> </div> @endif @endif + --> @yield('content') From d41d0a6e2dcf8d26a27b2bfef9ea2c2cc60969aa Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 12:29:28 +0200 Subject: [PATCH 130/514] ticket notify permission --- app/Http/Controllers/TicketsController.php | 23 +++++++++++----------- app/Settings/TicketSettings.php | 13 ------------ config/permissions_web.php | 1 + 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index d5912e435..591c8d851 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -58,17 +58,13 @@ public function store(Request $request, TicketSettings $ticket_settings) ); $ticket->save(); $user = Auth::user(); - switch ($ticket_settings->notify) { - case 'all': - $admin = User::where('role', 'admin')->orWhere('role', 'mod')->get(); - Notification::send($admin, new AdminCreateNotification($ticket, $user)); - case 'admin': - $admin = User::where('role', 'admin')->get(); - Notification::send($admin, new AdminCreateNotification($ticket, $user)); - case 'moderator': - $admin = User::where('role', 'mod')->get(); - Notification::send($admin, new AdminCreateNotification($ticket, $user)); + + $staffNotify = User::permission('admin.tickets.get_notification')->get(); + foreach($staffNotify as $staff){ + Notification::send($staff, new AdminCreateNotification($ticket, $user)); } + + $user->notify(new CreateNotification($ticket)); return redirect()->route('ticket.index')->with('success', __('A ticket has been opened, ID: #') . $ticket->ticket_id); @@ -112,9 +108,12 @@ public function reply(Request $request) 'message' => $request->input('message'), ]); $user = Auth::user(); - $admin = User::where('role', 'admin')->orWhere('role', 'mod')->get(); $newmessage = $request->input('ticketcomment'); - Notification::send($admin, new AdminReplyNotification($ticket, $user, $newmessage)); + + $staffNotify = User::permission('admin.tickets.get_notification')->get(); + foreach($staffNotify as $staff){ + Notification::send($staff, new AdminReplyNotification($ticket, $user, $newmessage)); + } return redirect()->back()->with('success', __('Your comment has been submitted')); } diff --git a/app/Settings/TicketSettings.php b/app/Settings/TicketSettings.php index 10ac16b14..34fde789c 100644 --- a/app/Settings/TicketSettings.php +++ b/app/Settings/TicketSettings.php @@ -7,7 +7,6 @@ class TicketSettings extends Settings { public bool $enabled; - public string $notify; public static function group(): string { @@ -22,7 +21,6 @@ public static function getValidations() { return [ 'enabled' => 'nullable|boolean', - 'notify' => 'nullable|string', ]; } @@ -40,17 +38,6 @@ public static function getOptionInputData() 'type' => 'boolean', 'description' => 'Enable or disable the ticket system.', ], - 'notify' => [ - 'label' => 'Notify', - 'type' => 'select', - 'description' => 'Who will receive an E-Mail when a new Ticket is created.', - 'options' => [ - 'admin' => 'Admins', - 'moderator' => 'Moderators', - 'all' => 'Admins and Moderators', - 'none' => 'Nobody', - ], - ], ]; } } diff --git a/config/permissions_web.php b/config/permissions_web.php index 0de102718..71c1cd1ed 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -15,6 +15,7 @@ 'admin.ticket.read', 'admin.tickets.write', + 'admin.tickets.get_notification', 'admin.ticket_blacklist.read', 'admin.ticket_blacklist.write', From 0899df594536fe15d71235a6e4af4f057ee29125 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 5 May 2023 14:06:38 +0200 Subject: [PATCH 131/514] OOM killer & Roles / Permissions API --- app/Classes/PterodactylClient.php | 2 + .../Controllers/Admin/ProductController.php | 5 + app/Http/Controllers/Api/RoleController.php | 155 ++++++++++++++++++ app/Http/Controllers/Api/UserController.php | 59 +++---- .../2023_05_05_103834_oom_killer.php | 32 ++++ .../views/admin/products/create.blade.php | 8 + .../views/admin/products/edit.blade.php | 10 ++ .../views/admin/products/index.blade.php | 2 + themes/default/views/layouts/main.blade.php | 2 +- themes/default/views/servers/create.blade.php | 6 + 10 files changed, 251 insertions(+), 30 deletions(-) create mode 100644 app/Http/Controllers/Api/RoleController.php create mode 100644 database/migrations/2023_05_05_103834_oom_killer.php diff --git a/app/Classes/PterodactylClient.php b/app/Classes/PterodactylClient.php index 8f1b116c1..7ac43a3e6 100644 --- a/app/Classes/PterodactylClient.php +++ b/app/Classes/PterodactylClient.php @@ -266,6 +266,7 @@ public function createServer(Server $server, Egg $egg, int $allocationId) 'docker_image' => $egg->docker_image, 'startup' => $egg->startup, 'environment' => $egg->getEnvironmentVariables(), + 'oom_disabled' => !$server->product->oom_killer, 'limits' => [ 'memory' => $server->product->memory, 'swap' => $server->product->swap, @@ -378,6 +379,7 @@ public function updateServer(Server $server, Product $product) 'io' => $product->io, 'cpu' => $product->cpu, 'threads' => null, + 'oom_disabled' => !$server->product->oom_killer, 'feature_limits' => [ 'databases' => $product->databases, 'backups' => $product->backups, diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 3dc95829b..bfa8bd5a6 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -87,6 +87,7 @@ public function store(Request $request) 'nodes.*' => 'required|exists:nodes,id', 'eggs.*' => 'required|exists:eggs,id', 'disabled' => 'nullable', + 'oom_killer' => 'nullable', ]); $disabled = ! is_null($request->input('disabled')); @@ -159,6 +160,7 @@ public function update(Request $request, Product $product): RedirectResponse 'nodes.*' => 'required|exists:nodes,id', 'eggs.*' => 'required|exists:eggs,id', 'disabled' => 'nullable', + 'oom_killer' => 'nullable', ]); $disabled = ! is_null($request->input('disabled')); @@ -256,6 +258,9 @@ public function dataTable() ->editColumn('minimum_credits', function (Product $product, UserSettings $user_settings) { return $product->minimum_credits==-1 ? $user_settings->min_credits_to_make_server : $product->minimum_credits; }) + ->editColumn('oom_killer', function (Product $product, UserSettings $user_settings) { + return $product->oom_killer ? __("enabled") : __("disabled"); + }) ->editColumn('created_at', function (Product $product) { return $product->created_at ? $product->created_at->diffForHumans() : ''; }) diff --git a/app/Http/Controllers/Api/RoleController.php b/app/Http/Controllers/Api/RoleController.php new file mode 100644 index 000000000..190dd6ccb --- /dev/null +++ b/app/Http/Controllers/Api/RoleController.php @@ -0,0 +1,155 @@ +<?php + +namespace App\Http\Controllers\Api; + +use App\Http\Controllers\Controller; +use App\Models\User; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Database\Eloquent\Collection; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Http\Request; +use Illuminate\Http\Response; +use Illuminate\Validation\Rule; +use Spatie\Permission\Models\Role; +use Spatie\QueryBuilder\QueryBuilder; + +class RoleController extends Controller +{ + const ALLOWED_INCLUDES = ['permissions', 'users']; + + const ALLOWED_FILTERS = ['name']; + + /** + * Display a listing of the resource. + * + * @return LengthAwarePaginator + */ + public function index(Request $request) + { + $query = QueryBuilder::for(Role::class) + ->allowedIncludes(self::ALLOWED_INCLUDES) + ->allowedFilters(self::ALLOWED_FILTERS); + + return $query->paginate($request->input('per_page') ?? 50); + } + + /** + * Show the form for creating a new resource. + * + * @return Response + */ + public function create() + { + // + } + + /** + * Store a newly created resource in storage. + * + * @param Request $request + * @return Response + */ + public function store(Request $request) + { + $request->validate([ + 'name' => 'nullable|string|max:191', + 'color' => [ + 'required', + 'regex:/^#([a-f0-9]{6}|[a-f0-9]{3})$/i' + ], + 'power' => 'required', + ]); + + $role = Role::create([ + 'name' => $request->name, + 'color' => $request->color, + 'power' => $request->power, + ]); + + if ($request->permissions) { + $role->givePermissionTo($request->permissions); + } + + return $role; + } + + /** + * Display the specified resource. + * + * @param int $id + * @return Role|Collection|Model + */ + public function show(int $id) + { + $query = QueryBuilder::for(Role::class) + ->where('id', '=', $id) + ->allowedIncludes(self::ALLOWED_INCLUDES); + + return $query->firstOrFail(); + } + + /** + * Show the form for editing the specified resource. + * + * @param int $id + * @return Response + */ + public function edit($id) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param Request $request + * @param int $id + * @return Response + */ + public function update(Request $request, int $id) + { + $role = Role::findOrFail($id); + + $request->validate([ + 'name' => 'nullable|string|max:191', + 'color' => [ + 'required', + 'regex:/^#([a-f0-9]{6}|[a-f0-9]{3})$/i' + ], + 'power' => 'required', + ]); + + if ($request->permissions) { + $role->givePermissionTo($request->permissions); + } + + $role->update($request->all()); + //TODO PERMISSIONS? + return $role; + } + + /** + * Remove the specified resource from storage. + * + * @param int $id + * @return Response + */ + public function destroy(int $id) + { + $role = Role::findOrFail($id); + + if($role->id == 1 || $role->id == 3|| $role->id == 4){ //cannot delete admin and User role + return response()->json([ + 'error' => 'Not allowed to delete Admin, Client or Member'], 400); + } + + $users = User::role($role)->get(); + + foreach($users as $user){ + $user->syncRoles([4]); + } + $role->delete(); + + return $role; + } +} diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php index 1a6ef3ebe..0dd445be3 100644 --- a/app/Http/Controllers/Api/UserController.php +++ b/app/Http/Controllers/Api/UserController.php @@ -2,16 +2,12 @@ namespace App\Http\Controllers\Api; +use App\Classes\Pterodactyl; use App\Events\UserUpdateCreditsEvent; use App\Http\Controllers\Controller; use App\Models\DiscordUser; use App\Models\User; use App\Notifications\ReferralNotification; -use App\Traits\Referral; -use App\Settings\PterodactylSettings; -use App\Classes\PterodactylClient; -use App\Settings\ReferralSettings; -use App\Settings\UserSettings; use Carbon\Carbon; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Pagination\LengthAwarePaginator; @@ -31,18 +27,9 @@ class UserController extends Controller { - use Referral; + const ALLOWED_INCLUDES = ['servers', 'notifications', 'payments', 'vouchers', 'roles', 'discordUser']; - const ALLOWED_INCLUDES = ['servers', 'notifications', 'payments', 'vouchers', 'discordUser']; - - const ALLOWED_FILTERS = ['name', 'server_limit', 'email', 'pterodactyl_id', 'role', 'suspended']; - - private $pterodactyl; - - public function __construct(PterodactylSettings $ptero_settings) - { - $this->pterodactyl = new PterodactylClient($ptero_settings); - } + const ALLOWED_FILTERS = ['name', 'server_limit', 'email', 'pterodactyl_id', 'suspended']; /** * Display a listing of the resource. @@ -98,14 +85,13 @@ public function update(Request $request, int $id) 'email' => 'sometimes|string|email', 'credits' => 'sometimes|numeric|min:0|max:1000000', 'server_limit' => 'sometimes|numeric|min:0|max:1000000', - 'role' => ['sometimes', Rule::in(['admin', 'moderator', 'client', 'member'])], ]); event(new UserUpdateCreditsEvent($user)); //Update Users Password on Pterodactyl //Username,Mail,First and Lastname are required aswell - $response = $this->pterodactyl->application->patch('/application/users/' . $user->pterodactyl_id, [ + $response = Pterodactyl::client()->patch('/application/users/'.$user->pterodactyl_id, [ 'username' => $request->name, 'first_name' => $request->name, 'last_name' => $request->name, @@ -213,7 +199,7 @@ public function decrement(Request $request, int $id) * * @throws ValidationException */ - public function suspend(int $id) + public function suspend(Request $request, int $id) { $discordUser = DiscordUser::find($id); $user = $discordUser ? $discordUser->user : User::findOrFail($id); @@ -237,12 +223,12 @@ public function suspend(int $id) * * @throws ValidationException */ - public function unsuspend(int $id) + public function unsuspend(Request $request, int $id) { $discordUser = DiscordUser::find($id); $user = $discordUser ? $discordUser->user : User::findOrFail($id); - if (!$user->isSuspended()) { + if (! $user->isSuspended()) { throw ValidationException::withMessages([ 'error' => 'You cannot unsuspend an User who is not suspended.', ]); @@ -253,10 +239,25 @@ public function unsuspend(int $id) return $user; } + /** + * Create a unique Referral Code for User + * + * @return string + */ + protected function createReferralCode() + { + $referralcode = STR::random(8); + if (User::where('referral_code', '=', $referralcode)->exists()) { + $this->createReferralCode(); + } + + return $referralcode; + } + /** * @throws ValidationException */ - public function store(Request $request, UserSettings $user_settings, ReferralSettings $referral_settings) + public function store(Request $request) { $request->validate([ 'name' => ['required', 'string', 'max:30', 'min:4', 'alpha_num', 'unique:users'], @@ -265,7 +266,7 @@ public function store(Request $request, UserSettings $user_settings, ReferralSet ]); // Prevent the creation of new users via API if this is enabled. - if (!$user_settings->creation_enabled) { + if (! config('SETTINGS::SYSTEM:CREATION_OF_NEW_USERS', 'true')) { throw ValidationException::withMessages([ 'error' => 'The creation of new users has been blocked by the system administrator.', ]); @@ -274,13 +275,13 @@ public function store(Request $request, UserSettings $user_settings, ReferralSet $user = User::create([ 'name' => $request->input('name'), 'email' => $request->input('email'), - 'credits' => $user_settings->initial_credits, - 'server_limit' => $user_settings->initial_server_limit, + 'credits' => config('SETTINGS::USER:INITIAL_CREDITS', 150), + 'server_limit' => config('SETTINGS::USER:INITIAL_SERVER_LIMIT', 1), 'password' => Hash::make($request->input('password')), 'referral_code' => $this->createReferralCode(), ]); - $response = $this->pterodactyl->application->post('/application/users', [ + $response = Pterodactyl::client()->post('/application/users', [ 'external_id' => App::environment('local') ? Str::random(16) : (string) $user->id, 'username' => $user->name, 'email' => $user->email, @@ -303,12 +304,12 @@ public function store(Request $request, UserSettings $user_settings, ReferralSet 'pterodactyl_id' => $response->json()['attributes']['id'], ]); //INCREMENT REFERRAL-USER CREDITS - if (!empty($request->input('referral_code'))) { + if (! empty($request->input('referral_code'))) { $ref_code = $request->input('referral_code'); $new_user = $user->id; if ($ref_user = User::query()->where('referral_code', '=', $ref_code)->first()) { - if ($referral_settings->mode === 'register' || $referral_settings->mode === 'both') { - $ref_user->increment('credits', $referral_settings->reward); + if (config('SETTINGS::REFERRAL:MODE') == 'register' || config('SETTINGS::REFERRAL:MODE') == 'both') { + $ref_user->increment('credits', config('SETTINGS::REFERRAL::REWARD')); $ref_user->notify(new ReferralNotification($ref_user->id, $new_user)); } //INSERT INTO USER_REFERRALS TABLE diff --git a/database/migrations/2023_05_05_103834_oom_killer.php b/database/migrations/2023_05_05_103834_oom_killer.php new file mode 100644 index 000000000..3fecb34ef --- /dev/null +++ b/database/migrations/2023_05_05_103834_oom_killer.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('products', function (Blueprint $table) { + $table->boolean('oom_killer')->after("allocations")->default(false); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('products', function (Blueprint $table) { + $table->dropColumn('oom_killer'); + }); + } +}; diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index 0922fc4b2..853fa9b0b 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -131,7 +131,15 @@ class="form-control @error('description') is-invalid @enderror" </div> @enderror </div> + <div class="form-group"> + <input type="checkbox" value="1" id="oom" name="oom_killer" + class=""> + <label for="oom_killer">{{__('OOM Killer')}} <i + data-toggle="popover" data-trigger="hover" + data-content="{{__('Enable or Disable the OOM Killer for this Product.')}}" + class="fas fa-info-circle"></i></label> + </div> </div> <div class="col-lg-6"> <div class="form-group"> diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index 4a0d9e24f..8e909af11 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -139,6 +139,16 @@ class="form-control @error('description') is-invalid @enderror" @enderror </div> + <div class="form-group"> + <input type="checkbox" @if($product->oom_killer) checked @endif value="1" id="oom" name="oom_killer" + class=""> + + <label for="oom_killer">{{__('OOM Killer')}} <i + data-toggle="popover" data-trigger="hover" + data-content="{{__('Enable or Disable the OOM Killer for this Product.')}}" + class="fas fa-info-circle"></i></label> + </div> + </div> <div class="col-lg-6"> <div class="form-group"> diff --git a/themes/default/views/admin/products/index.blade.php b/themes/default/views/admin/products/index.blade.php index 130a2e1d3..f4eea091f 100644 --- a/themes/default/views/admin/products/index.blade.php +++ b/themes/default/views/admin/products/index.blade.php @@ -53,6 +53,7 @@ class="fas fa-plus mr-1"></i>{{__('Create new')}}</a> <th>{{__('Nodes')}}</th> <th>{{__('Eggs')}}</th> <th>{{__('Min Credits')}}</th> + <th>{{__('OOM Killer')}}</th> <th>{{__('Servers')}}</th> <th>{{__('Created at')}}</th> <th>{{ __('Actions') }}</th> @@ -101,6 +102,7 @@ function submitResult() { {data: "nodes", sortable: false}, {data: "eggs", sortable: false}, {data: "minimum_credits"}, + {data: "oom_killer"}, {data: "servers", sortable: false}, {data: "created_at"}, {data: "actions", sortable: false} diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index bff2fe7f9..994f2bfc6 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -18,7 +18,7 @@ href="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('favicon.ico') }}" type="image/x-icon"> - <script src="{{ asset('plugins/alpinejs/3.12.0_cdn.min.js') }}"></script> + <script src="{{ asset('plugins/alpinejs/3.12.0_cdn.min.js') }}" defer></script> {{-- <link rel="stylesheet" href="{{asset('css/adminlte.min.css')}}"> --}} <link rel="stylesheet" href="{{ asset('plugins/datatables/jquery.dataTables.min.css') }}"> diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index f8e9a5d40..9cef3a98e 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -201,6 +201,12 @@ class="custom-select"> {{ __('Databases') }}</span> <span class="d-inline-block" x-text="product.databases"></span> </li> + <li class="d-flex justify-content-between"> + <span class="d-inline-block"><i class="fas fa-skull-crossbones"></i> + {{ __('OOM Killer') }}</span> + <span class="d-inline-block" + x-text="product.oom_killer == 1 ? 'enabled' : 'disabled'"></span> + </li> <li class="d-flex justify-content-between"> <span class="d-inline-block"><i class="fas fa-network-wired"></i> {{ __('Allocations') }} From 587e071b87dbe493d5150984061d2556c4f089c8 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 17:38:25 +0200 Subject: [PATCH 132/514] Refactor "Moderator" views --- .../default/views/{moderator => admin}/ticket/blacklist.blade.php | 0 .../default/views/{moderator => admin}/ticket/category.blade.php | 0 themes/default/views/{moderator => admin}/ticket/index.blade.php | 0 themes/default/views/{moderator => admin}/ticket/show.blade.php | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename themes/default/views/{moderator => admin}/ticket/blacklist.blade.php (100%) rename themes/default/views/{moderator => admin}/ticket/category.blade.php (100%) rename themes/default/views/{moderator => admin}/ticket/index.blade.php (100%) rename themes/default/views/{moderator => admin}/ticket/show.blade.php (100%) diff --git a/themes/default/views/moderator/ticket/blacklist.blade.php b/themes/default/views/admin/ticket/blacklist.blade.php similarity index 100% rename from themes/default/views/moderator/ticket/blacklist.blade.php rename to themes/default/views/admin/ticket/blacklist.blade.php diff --git a/themes/default/views/moderator/ticket/category.blade.php b/themes/default/views/admin/ticket/category.blade.php similarity index 100% rename from themes/default/views/moderator/ticket/category.blade.php rename to themes/default/views/admin/ticket/category.blade.php diff --git a/themes/default/views/moderator/ticket/index.blade.php b/themes/default/views/admin/ticket/index.blade.php similarity index 100% rename from themes/default/views/moderator/ticket/index.blade.php rename to themes/default/views/admin/ticket/index.blade.php diff --git a/themes/default/views/moderator/ticket/show.blade.php b/themes/default/views/admin/ticket/show.blade.php similarity index 100% rename from themes/default/views/moderator/ticket/show.blade.php rename to themes/default/views/admin/ticket/show.blade.php From 5e46bbbe7e75109b581126d2ac877f9bc68c68db Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 18:43:28 +0200 Subject: [PATCH 133/514] Cleanup Middleware, Fix locale --- .../Controllers/Admin/SettingsController.php | 6 +++- app/Http/Kernel.php | 3 -- app/Http/Middleware/GlobalNames.php | 25 ----------------- app/Http/Middleware/isAdmin.php | 28 ------------------- app/Http/Middleware/isMod.php | 27 ------------------ .../views/admin/settings/index.blade.php | 4 +-- 6 files changed, 7 insertions(+), 86 deletions(-) delete mode 100644 app/Http/Middleware/GlobalNames.php delete mode 100644 app/Http/Middleware/isAdmin.php delete mode 100644 app/Http/Middleware/isMod.php diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 5b05db2b5..7d1657f1d 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -119,16 +119,20 @@ public function update(Request $request) $rp = new \ReflectionProperty($settingsClass, $key); $rpType = $rp->getType(); + if ($rpType == 'bool') { $settingsClass->$key = $request->has($key); continue; } + if ($rp->name == 'available') { + $settingsClass->$key = implode(",",$request->$key); + continue; + } $nullable = $rpType->allowsNull(); if ($nullable) $settingsClass->$key = $request->input($key) ?? null; else $settingsClass->$key = $request->input($key); } - $settingsClass->save(); diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index a6fb149b0..d0b1c7f7e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -4,7 +4,6 @@ use App\Http\Middleware\ApiAuthToken; use App\Http\Middleware\CheckSuspended; -use App\Http\Middleware\GlobalNames; use App\Http\Middleware\isAdmin; use App\Http\Middleware\isMod; use App\Http\Middleware\LastSeen; @@ -44,14 +43,12 @@ class Kernel extends HttpKernel \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, LastSeen::class, - GlobalNames::class, \App\Http\Middleware\SetLocale::class, ], 'api' => [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, - GlobalNames::class, ], ]; diff --git a/app/Http/Middleware/GlobalNames.php b/app/Http/Middleware/GlobalNames.php deleted file mode 100644 index 9250f23bf..000000000 --- a/app/Http/Middleware/GlobalNames.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php - -namespace App\Http\Middleware; - -use Closure; -use Illuminate\Http\Request; - -class GlobalNames -{ - /** - * Handle an incoming request. - * - * @param Request $request - * @param Closure $next - * @return mixed - */ - public function handle(Request $request, Closure $next) - { - $unsupported_lang_array = explode(',', config('app.unsupported_locales')); - $unsupported_lang_array = array_map('strtolower', $unsupported_lang_array); - define('UNSUPPORTED_LANGS', $unsupported_lang_array); - - return $next($request); - } -} diff --git a/app/Http/Middleware/isAdmin.php b/app/Http/Middleware/isAdmin.php deleted file mode 100644 index 6e8202a43..000000000 --- a/app/Http/Middleware/isAdmin.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -namespace App\Http\Middleware; - -use App\Providers\RouteServiceProvider; -use Closure; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; - -class isAdmin -{ - /** - * Handle an incoming request. - * - * @param Request $request - * @param Closure $next - * @return mixed - */ - public function handle(Request $request, Closure $next) - { - //if (Auth::user() && Auth::user()->hasRole("Admin")) { - if (Auth::user() && Auth::user()->hasRole(1)) { - return $next($request); - } - - return redirect(RouteServiceProvider::HOME); - } -} diff --git a/app/Http/Middleware/isMod.php b/app/Http/Middleware/isMod.php deleted file mode 100644 index 51d6db851..000000000 --- a/app/Http/Middleware/isMod.php +++ /dev/null @@ -1,27 +0,0 @@ -<?php - -namespace App\Http\Middleware; - -use App\Providers\RouteServiceProvider; -use Closure; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; - -class isMod -{ - /** - * Handle an incoming request. - * - * @param Request $request - * @param Closure $next - * @return mixed - */ - public function handle(Request $request, Closure $next) - { - if (Auth::user() && Auth::user()->role == 'moderator' || Auth::user() && Auth::user()->hasRole(1)) { - return $next($request); - } - - return redirect(RouteServiceProvider::HOME); - } -} diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 2c63d4280..db4344b6e 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -132,11 +132,11 @@ class="custom-select w-100" name="{{ $key }}"> @case($value['type'] == 'multiselect') <select id="{{ $key }}" - class="custom-select w-100" name="{{ $key }}" + class="custom-select w-100" name="{{ $key }}[]" multiple> @foreach ($value['options'] as $option) <option value="{{ $option }}" - {{ $value['value'] == $option ? 'selected' : '' }}> + {{ strpos($value['value'],$option) !== false ? 'selected' : '' }}> {{ __($option) }} </option> @endforeach From f72447dfa13576029cb75915b22377ac886f5ba5 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 18:57:57 +0200 Subject: [PATCH 134/514] fix view of partner programm --- themes/default/views/home.blade.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/themes/default/views/home.blade.php b/themes/default/views/home.blade.php index 119c50ffe..8993972c7 100644 --- a/themes/default/views/home.blade.php +++ b/themes/default/views/home.blade.php @@ -203,9 +203,7 @@ class="info-box-text">{{ __('Out of Credits in', ['credits' => $general_settings </div> <!-- /.card-header --> <div class="card-body py-0 pb-2"> - @if ( - ($referral_settings->allowed == 'client' && Auth::user()->role != 'member') || - $referral_settings->allowed == 'everyone') + @if (Auth::user()->can("user.referral")) <div class="row"> <div class="mt-3 col-md-8"> <span class="badge badge-success" style="font-size: 14px"> @@ -254,15 +252,14 @@ class="info-box-text">{{ __('Out of Credits in', ['credits' => $general_settings <table class="table"> <thead> <tr> - <th>{{ __('Reward per registered user') }}</th> - <th>{{ __('New user payment commision') }}</th> + @if(in_array($referral_settings->mode, ["Commission","Both"]))<th>{{ __('Reward per registered user') }}</th> @endif + @if(in_array($referral_settings->mode, ["Sign-Up","Both"]))<th>{{ __('New user payment commision') }}</th> @endif </tr> </thead> <tbody> <tr> - <td>{{ $referral_settings->reward }} - {{ $general_settings->credits_display_name }}</td> - <td>{{ $referral_settings->percentage }}%</td> + @if(in_array($referral_settings->mode, ["Commission","Both"]))<td>{{ $referral_settings->reward }} {{ $general_settings->credits_display_name }}</td> @endif + @if(in_array($referral_settings->mode, ["Sign-Up","Both"]))<td>{{ $referral_settings->percentage }}%</td> @endif </tr> </tbody> </table> From 28afc71d7f26e37a2493742fbbb6b24d4d303804 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 19:05:50 +0200 Subject: [PATCH 135/514] remove "allowed" on referral. its now a permission --- app/Settings/ReferralSettings.php | 12 +----------- .../2023_02_01_182135_create_referral_settings.php | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/app/Settings/ReferralSettings.php b/app/Settings/ReferralSettings.php index e49fbaae3..47d086465 100644 --- a/app/Settings/ReferralSettings.php +++ b/app/Settings/ReferralSettings.php @@ -6,7 +6,6 @@ class ReferralSettings extends Settings { - public string $allowed; public bool $always_give_commission; public bool $enabled; public ?float $reward; @@ -43,19 +42,10 @@ public static function getOptionInputData() { return [ 'category_icon' => 'fas fa-user-friends', - 'allowed' => [ - 'label' => 'Allowed', - 'type' => 'select', - 'description' => 'Who is allowed to see their referral-URL', - 'options' => [ - 'everyone' => 'Everyone', - 'clients' => 'Clients', - ], - ], 'always_give_commission' => [ 'label' => 'Always Give Commission', 'type' => 'boolean', - 'description' => 'Always give commission to the referrer.', + 'description' => 'Always give commission to the referrer or only on the first Purchase.', ], 'enabled' => [ 'label' => 'Enabled', diff --git a/database/settings/2023_02_01_182135_create_referral_settings.php b/database/settings/2023_02_01_182135_create_referral_settings.php index 8a2cde829..5727557b1 100644 --- a/database/settings/2023_02_01_182135_create_referral_settings.php +++ b/database/settings/2023_02_01_182135_create_referral_settings.php @@ -10,7 +10,6 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('referral.allowed', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ALLOWED') : 'client'); $this->migrator->add('referral.always_give_commission', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION') : false); $this->migrator->add('referral.enabled', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ENABLED') : false); $this->migrator->add('referral.reward', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::REWARD') : 100); From a2a54262528f8ebc8692e015cdb16faeb60b54fd Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 19:45:50 +0200 Subject: [PATCH 136/514] fix cloning --- app/Http/Controllers/Admin/ProductController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index bfa8bd5a6..c05a13032 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -52,12 +52,13 @@ public function create(GeneralSettings $general_settings) ]); } - public function clone(Product $product) + public function clone(Product $product, GeneralSettings $general_settings) { $this->checkPermission(self::WRITE_PERMISSION); return view('admin.products.create', [ 'product' => $product, + 'credits_display_name' => $general_settings->credits_display_name, 'locations' => Location::with('nodes')->get(), 'nests' => Nest::with('eggs')->get(), ]); From 8279f071569c0b4e77b0f24e6c4b6df8086fd71c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 19:49:05 +0200 Subject: [PATCH 137/514] new Permission "admin.servers.bypass_creation_enabled" --- app/Http/Controllers/ServerController.php | 2 +- config/permissions_web.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index acd4392e3..30262cac0 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -164,7 +164,7 @@ private function validateConfigurationRules(UserSettings $user_settings, ServerS } //Required Verification for creating an server - if (!$server_settings->creation_enabled && Auth::user()->role != 'admin') { + if (!$server_settings->creation_enabled && Auth::user()->cannot("admin.servers.bypass_creation_enabled")) { return redirect()->route('servers.index')->with('error', __('The system administrator has blocked the creation of new servers.')); } diff --git a/config/permissions_web.php b/config/permissions_web.php index 71c1cd1ed..0ae789ca7 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -46,6 +46,7 @@ 'admin.servers.write.owner', 'admin.servers.write.identifier', 'admin.servers.delete', + 'admin.servers.bypass_creation_enabled', 'admin.products.read', 'admin.products.create', From bc56f713ad84899540bcf794f736843e583f9216 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 7 May 2023 21:24:26 +0200 Subject: [PATCH 138/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Call=20parent=20c?= =?UTF-8?q?onstructor=20on=20model?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Models/Server.php | 2 ++ app/Models/User.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/Models/Server.php b/app/Models/Server.php index 21d2c2456..8fb64c02e 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -69,6 +69,8 @@ public function getActivitylogOptions(): LogOptions public function __construct() { + parent::__construct(); + $ptero_settings = new PterodactylSettings(); $this->pterodactyl = new PterodactylClient($ptero_settings); } diff --git a/app/Models/User.php b/app/Models/User.php index d25a71009..eabbf0cbf 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -93,6 +93,8 @@ class User extends Authenticatable implements MustVerifyEmail public function __construct() { + parent::__construct(); + $ptero_settings = new PterodactylSettings(); $this->pterodactyl = new PterodactylClient($ptero_settings); } From a32d42feabd0ef278d195c961f9a88b3d4c93d88 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 21:41:26 +0200 Subject: [PATCH 139/514] OOM Killer --- app/Http/Controllers/Admin/ProductController.php | 7 +++++-- themes/default/views/servers/index.blade.php | 2 +- themes/default/views/servers/settings.blade.php | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index c05a13032..33d98c504 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -91,8 +91,10 @@ public function store(Request $request) 'oom_killer' => 'nullable', ]); + $disabled = ! is_null($request->input('disabled')); - $product = Product::create(array_merge($request->all(), ['disabled' => $disabled])); + $oomkiller = ! is_null($request->input('oom_killer')); + $product = Product::create(array_merge($request->all(), ['disabled' => $disabled, 'oom_killer' => $oomkiller])); //link nodes and eggs $product->eggs()->attach($request->input('eggs')); @@ -165,7 +167,8 @@ public function update(Request $request, Product $product): RedirectResponse ]); $disabled = ! is_null($request->input('disabled')); - $product->update(array_merge($request->all(), ['disabled' => $disabled])); + $oomkiller = ! is_null($request->input('oom_killer')); + $product->update(array_merge($request->all(), ['disabled' => $disabled, 'oom_killer' => $oomkiller])); //link nodes and eggs $product->eggs()->detach(); diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index df47ae53f..93f31b7d2 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -103,7 +103,7 @@ class="fas fa-info-circle"></i> <span>{{ $server->product->name }} </span> <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/>" + data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/>{{ __('OOM Killer') }}: {{ $server->product->oom_killer ? __("enabled") : __("disabled") }} <br/>" class="fas fa-info-circle"></i> </div> diff --git a/themes/default/views/servers/settings.blade.php b/themes/default/views/servers/settings.blade.php index 4ef717443..9525cd238 100644 --- a/themes/default/views/servers/settings.blade.php +++ b/themes/default/views/servers/settings.blade.php @@ -204,6 +204,18 @@ </div> </div> </div> + <div class="col-lg-6"> + <div class="row"> + <div class="col-lg-4"> + <label>{{__('OOM Killer')}}</label> + </div> + <div class="col-lg-8"> + <span style="max-width: 250px;" class="d-inline-block text-truncate"> + {{ $server->product->oom_killer ? __("enabled") : __("disabled") }} + </span> + </div> + </div> + </div> <div class="col-lg-6"> <div class="row"> <div class="col-lg-4"> From b07238cbed2f5ac8ac3699bc02a266373856a302 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 21:57:13 +0200 Subject: [PATCH 140/514] customizable ticket information --- app/Http/Controllers/TicketsController.php | 3 ++- app/Settings/TicketSettings.php | 7 +++++++ .../2023_02_04_181156_create_ticket_settings.php | 1 - .../settings/2023_05_07_195343_ticket_information.php | 11 +++++++++++ themes/default/views/ticket/index.blade.php | 3 +-- 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 database/settings/2023_05_07_195343_ticket_information.php diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index 591c8d851..de59bf883 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -23,9 +23,10 @@ class TicketsController extends Controller { const READ_PERMISSION = 'user.ticket.read'; const WRITE_PERMISSION = 'user.ticket.write'; - public function index(LocaleSettings $locale_settings) + public function index(LocaleSettings $locale_settings, TicketSettings $ticketSettings) { return view('ticket.index', [ + 'ticketsettings' => $ticketSettings, 'tickets' => Ticket::where('user_id', Auth::user()->id)->paginate(10), 'ticketcategories' => TicketCategory::all(), 'locale_datatables' => $locale_settings->datatables diff --git a/app/Settings/TicketSettings.php b/app/Settings/TicketSettings.php index 34fde789c..cd621b173 100644 --- a/app/Settings/TicketSettings.php +++ b/app/Settings/TicketSettings.php @@ -7,6 +7,7 @@ class TicketSettings extends Settings { public bool $enabled; + public ?string $information; public static function group(): string { @@ -21,6 +22,7 @@ public static function getValidations() { return [ 'enabled' => 'nullable|boolean', + 'information' => 'nullable|string', ]; } @@ -38,6 +40,11 @@ public static function getOptionInputData() 'type' => 'boolean', 'description' => 'Enable or disable the ticket system.', ], + 'information' => [ + 'label' => 'Ticket Information', + 'type' => 'textarea', + 'description' => 'Message shown on the right side when users create a new ticket.', + ], ]; } } diff --git a/database/settings/2023_02_04_181156_create_ticket_settings.php b/database/settings/2023_02_04_181156_create_ticket_settings.php index fcdcd588e..701ca14ae 100644 --- a/database/settings/2023_02_04_181156_create_ticket_settings.php +++ b/database/settings/2023_02_04_181156_create_ticket_settings.php @@ -11,7 +11,6 @@ public function up(): void // Get the user-set configuration values from the old table. $this->migrator->add('ticket.enabled', $table_exists ? $this->getOldValue('SETTINGS::TICKET:ENABLED') : 'all'); - $this->migrator->add('ticket.notify', $table_exists ? $this->getOldValue('SETTINGS::TICKET:NOTIFY') : 'all'); } public function down(): void diff --git a/database/settings/2023_05_07_195343_ticket_information.php b/database/settings/2023_05_07_195343_ticket_information.php new file mode 100644 index 000000000..a4d89f25c --- /dev/null +++ b/database/settings/2023_05_07_195343_ticket_information.php @@ -0,0 +1,11 @@ +<?php + +use Spatie\LaravelSettings\Migrations\SettingsMigration; + +return new class extends SettingsMigration +{ + public function up(): void + { + $this->migrator->add('ticket.information', "Can't start your server? Need an additional port? Do you have any other questions? Let us know by opening a ticket."); + } +}; diff --git a/themes/default/views/ticket/index.blade.php b/themes/default/views/ticket/index.blade.php index 0c1d40988..aae642215 100644 --- a/themes/default/views/ticket/index.blade.php +++ b/themes/default/views/ticket/index.blade.php @@ -66,8 +66,7 @@ class="fas fa-info-circle"></i></h5> </div> <div class="card-body"> - <p>{{__("Can't start your server? Need an additional port? Do you have any other questions? Let us know by - opening a ticket.")}}</p> + <p>{!! $ticketsettings->information !!}</p> </div> </div> From 3d8a1cf53fa69047e082ab17e865f85937dc31bf Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 7 May 2023 22:36:50 +0200 Subject: [PATCH 141/514] notify groups (check validation needed) --- app/Http/Controllers/Admin/UserController.php | 20 ++++++++++++++----- .../views/admin/users/notifications.blade.php | 12 +++++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 2edc3b7e5..4956e565c 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -272,7 +272,9 @@ public function notifications() { $this->checkPermission(self::NOTIFY_PERMISSION); - return view('admin.users.notifications'); + $roles = Role::all(); + + return view('admin.users.notifications')->with(["roles" => $roles]); } /** @@ -288,12 +290,14 @@ public function notify(Request $request) { $this->checkPermission(self::NOTIFY_PERMISSION); +//TODO: reimplement the required validation on all,users and roles . didnt work -- required_without:users,roles $data = $request->validate([ 'via' => 'required|min:1|array', 'via.*' => 'required|string|in:mail,database', - 'all' => 'required_without:users|boolean', - 'users' => 'required_without:all|min:1|array', - 'users.*' => 'exists:users,id', + 'all' => 'boolean', + 'users' => 'min:1|array', + 'roles' => 'min:1|array', + 'roles.*' => 'required_without:all,users|exists:roles,id', 'title' => 'required|string|min:1', 'content' => 'required|string|min:1', ]); @@ -312,7 +316,13 @@ public function notify(Request $request) ->line(new HtmlString($data['content'])); } $all = $data['all'] ?? false; - $users = $all ? User::all() : User::whereIn('id', $data['users'])->get(); + if(!$data["roles"]){ + $users = $all ? User::all() : User::whereIn('id', $data['users'])->get(); + } else{ + $users = User::role($data["roles"])->get(); + } + + try { Notification::send($users, new DynamicNotification($data['via'], $database, $mail)); } catch (Exception $e) { diff --git a/themes/default/views/admin/users/notifications.blade.php b/themes/default/views/admin/users/notifications.blade.php index 53a87f19a..e29303add 100644 --- a/themes/default/views/admin/users/notifications.blade.php +++ b/themes/default/views/admin/users/notifications.blade.php @@ -33,13 +33,20 @@ @method('POST') <div class="form-group"> - <label>{{__('Users')}}</label><br> <input id="all" name="all" type="checkbox" value="1" onchange="toggleClass('users-form', 'd-none')"> - <label for="all">{{__('All')}}</label> + <label for="all">{{__('All')}}</label><br> <div id="users-form"> + <label>{{__('Users')}}</label><br> <select id="users" name="users[]" class="form-control" multiple></select> + + <label>{{__('Roles')}}</label><br> + <select id="roles" name="roles[]" onchange="toggleClass('users', 'd-none')" class="form-control" multiple> + @foreach($roles as $role) + <option value="{{$role->id}}">{{$role->name}}</option> + @endforeach + </select> </div> @error('all') <div class="invalid-feedback d-block"> @@ -126,6 +133,7 @@ class="form-control @error('content') is-invalid @enderror"> }) function initUserSelect(data) { + $('#roles').select2(); $('#users').select2({ ajax: { url: '/admin/users.json', From 6ef0b63c36ba8162fb8f0c2d6595fb1b28fc3392 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 10:45:47 +0200 Subject: [PATCH 142/514] API Fixes || Missing: Update Role --- app/Http/Controllers/Api/RoleController.php | 17 +++++++++++------ app/Http/Controllers/Api/UserController.php | 5 ++++- routes/api.php | 3 +++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/RoleController.php b/app/Http/Controllers/Api/RoleController.php index 190dd6ccb..65e2cb69e 100644 --- a/app/Http/Controllers/Api/RoleController.php +++ b/app/Http/Controllers/Api/RoleController.php @@ -52,7 +52,7 @@ public function create() public function store(Request $request) { $request->validate([ - 'name' => 'nullable|string|max:191', + 'name' => 'required|string|max:191', 'color' => [ 'required', 'regex:/^#([a-f0-9]{6}|[a-f0-9]{3})$/i' @@ -67,7 +67,10 @@ public function store(Request $request) ]); if ($request->permissions) { - $role->givePermissionTo($request->permissions); + $permissions = explode(",",$request->permissions); + foreach($permissions as $permission){ + $role->givePermissionTo($permission); + } } return $role; @@ -111,7 +114,7 @@ public function update(Request $request, int $id) $role = Role::findOrFail($id); $request->validate([ - 'name' => 'nullable|string|max:191', + 'name' => 'required|string|max:191', 'color' => [ 'required', 'regex:/^#([a-f0-9]{6}|[a-f0-9]{3})$/i' @@ -120,11 +123,13 @@ public function update(Request $request, int $id) ]); if ($request->permissions) { - $role->givePermissionTo($request->permissions); + $permissions = explode(",",$request->permissions); + $role->syncPermissions($permissions); } - $role->update($request->all()); - //TODO PERMISSIONS? + + $role->update($request->except('permissions')); + return $role; } diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php index 0dd445be3..78e56da2f 100644 --- a/app/Http/Controllers/Api/UserController.php +++ b/app/Http/Controllers/Api/UserController.php @@ -104,7 +104,10 @@ public function update(Request $request, int $id) 'pterodactyl_error_status' => $response->toException()->getCode(), ]); } - $user->update($request->all()); + if($request->has("role")){ + $user->syncRoles($request->role); + } + $user->update($request->except('role')); return $user; } diff --git a/routes/api.php b/routes/api.php index 74333b749..141b7529d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,6 +1,7 @@ <?php use App\Http\Controllers\Api\NotificationController; +use App\Http\Controllers\Api\RoleController; use App\Http\Controllers\Api\ServerController; use App\Http\Controllers\Api\UserController; use App\Http\Controllers\Api\VoucherController; @@ -31,6 +32,8 @@ // Route::get('/vouchers/{voucher}/users' , [VoucherController::class , 'users']); Route::resource('vouchers', VoucherController::class)->except('create', 'edit'); + Route::resource('roles', RoleController::class); + Route::get('/notifications/{user}', [NotificationController::class, 'index']); Route::get('/notifications/{user}/{notification}', [NotificationController::class, 'view']); Route::post('/notifications', [NotificationController::class, 'send']); From 4a4f6bebdb2512aafe2245a31a6fce8bd5b15aa1 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 10:55:37 +0200 Subject: [PATCH 143/514] Role API Update --- app/Http/Controllers/Api/RoleController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/RoleController.php b/app/Http/Controllers/Api/RoleController.php index 65e2cb69e..928473f8d 100644 --- a/app/Http/Controllers/Api/RoleController.php +++ b/app/Http/Controllers/Api/RoleController.php @@ -114,12 +114,12 @@ public function update(Request $request, int $id) $role = Role::findOrFail($id); $request->validate([ - 'name' => 'required|string|max:191', + 'name' => 'sometimes|string|max:191', 'color' => [ - 'required', + 'sometimes', 'regex:/^#([a-f0-9]{6}|[a-f0-9]{3})$/i' ], - 'power' => 'required', + 'power' => 'sometimes', ]); if ($request->permissions) { From e965b5f5c43d7a5f8283e52e85bc83ff8634dea8 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 11:03:11 +0200 Subject: [PATCH 144/514] PermissionsSeeder in Migration --- database/migrations/2023_05_05_090127_role_power.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/database/migrations/2023_05_05_090127_role_power.php b/database/migrations/2023_05_05_090127_role_power.php index 9c8da51d0..1bf02c444 100644 --- a/database/migrations/2023_05_05_090127_role_power.php +++ b/database/migrations/2023_05_05_090127_role_power.php @@ -2,6 +2,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Schema; return new class extends Migration @@ -16,6 +17,10 @@ public function up() Schema::table('roles', function (Blueprint $table) { $table->integer('power')->after("color")->default(50); }); + + Artisan::call('db:seed', [ + '--class' => 'PermissionsSeeder', + ]); } /** From e6f02d0679094984a986e344a19e42ee3d045895 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 11:09:49 +0200 Subject: [PATCH 145/514] force the seeder --- database/migrations/2023_05_05_090127_role_power.php | 1 + 1 file changed, 1 insertion(+) diff --git a/database/migrations/2023_05_05_090127_role_power.php b/database/migrations/2023_05_05_090127_role_power.php index 1bf02c444..7eb0d9113 100644 --- a/database/migrations/2023_05_05_090127_role_power.php +++ b/database/migrations/2023_05_05_090127_role_power.php @@ -20,6 +20,7 @@ public function up() Artisan::call('db:seed', [ '--class' => 'PermissionsSeeder', + '--force' => true ]); } From ae9ab59dec6aeda9bad891f5e582529bfe24e7d3 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Mon, 8 May 2023 11:10:51 +0200 Subject: [PATCH 146/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Make=20migrations?= =?UTF-8?q?=20be=20compatible=20to=20installed=20addon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...022_07_21_234818_undo_decimal_in_price.php | 32 ------------------- ...092704_add_billing_period_to_products.php} | 8 +++-- ...8_094402_update_user_credits_datatype.php} | 3 +- ...5818_add_last_billed_field_to_servers.php} | 5 +++ ...4527_add_cancelation_to_servers_table.php} | 5 +++ lang/en.json | 1 - 6 files changed, 17 insertions(+), 37 deletions(-) delete mode 100644 database/migrations/2022_07_21_234818_undo_decimal_in_price.php rename database/migrations/{2022_06_16_092704_add_billing_period_to_products.php => 2023_05_08_092704_add_billing_period_to_products.php} (89%) rename database/migrations/{2022_06_16_094402_update_user_credits_datatype.php => 2023_05_08_094402_update_user_credits_datatype.php} (89%) rename database/migrations/{2022_06_16_095818_add_last_billed_field_to_servers.php => 2023_05_08_095818_add_last_billed_field_to_servers.php} (83%) rename database/migrations/{2022_07_21_234527_add_cancelation_to_servers_table.php => 2023_05_08_234527_add_cancelation_to_servers_table.php} (82%) diff --git a/database/migrations/2022_07_21_234818_undo_decimal_in_price.php b/database/migrations/2022_07_21_234818_undo_decimal_in_price.php deleted file mode 100644 index cf4abb69b..000000000 --- a/database/migrations/2022_07_21_234818_undo_decimal_in_price.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php - -use Illuminate\Database\Migrations\Migration; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Schema; - -class UndoDecimalInPrice extends Migration -{ - /** - * Run the migrations. - * - * @return void - */ - public function up() - { - Schema::table('products', function (Blueprint $table) { - $table->decimal('price', 15, 4)->change(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('products', function (Blueprint $table) { - $table->decimal('price',['11','2'])->change(); - }); - } -} diff --git a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php b/database/migrations/2023_05_08_092704_add_billing_period_to_products.php similarity index 89% rename from database/migrations/2022_06_16_092704_add_billing_period_to_products.php rename to database/migrations/2023_05_08_092704_add_billing_period_to_products.php index ceed9362f..c4a7ac9ac 100644 --- a/database/migrations/2022_06_16_092704_add_billing_period_to_products.php +++ b/database/migrations/2023_05_08_092704_add_billing_period_to_products.php @@ -14,11 +14,16 @@ class AddBillingPeriodToProducts extends Migration */ public function up() { + // User already has installed the addon before + if (Schema::hasColumn("products", "billing_period")) { + return; + } + Schema::table('products', function (Blueprint $table) { + $table->string('billing_period')->default("hourly"); $table->decimal('price', 15, 4)->change(); $table->decimal('minimum_credits', 15, 4)->change(); - }); DB::statement('UPDATE products SET billing_period="hourly"'); @@ -29,7 +34,6 @@ public function up() $price = $price / 30 / 24; DB::table('products')->where('id', $product->id)->update(['price' => $price]); } - } /** diff --git a/database/migrations/2022_06_16_094402_update_user_credits_datatype.php b/database/migrations/2023_05_08_094402_update_user_credits_datatype.php similarity index 89% rename from database/migrations/2022_06_16_094402_update_user_credits_datatype.php rename to database/migrations/2023_05_08_094402_update_user_credits_datatype.php index ed5922e86..292102ccc 100644 --- a/database/migrations/2022_06_16_094402_update_user_credits_datatype.php +++ b/database/migrations/2023_05_08_094402_update_user_credits_datatype.php @@ -26,8 +26,7 @@ public function up() public function down() { Schema::table('users', function (Blueprint $table) { - $table->unsignedFloat('credits')->default(250)->change(); - + $table->decimal('price', ['11', '2'])->change(); }); } } diff --git a/database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php b/database/migrations/2023_05_08_095818_add_last_billed_field_to_servers.php similarity index 83% rename from database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php rename to database/migrations/2023_05_08_095818_add_last_billed_field_to_servers.php index 6b05f3a5b..9d43ae96d 100644 --- a/database/migrations/2022_06_16_095818_add_last_billed_field_to_servers.php +++ b/database/migrations/2023_05_08_095818_add_last_billed_field_to_servers.php @@ -14,6 +14,11 @@ class AddLastBilledFieldToServers extends Migration */ public function up() { + // User already has installed the addon before + if (Schema::hasColumn("servers", "last_billed")) { + return; + } + Schema::table('servers', function (Blueprint $table) { $table->dateTime('last_billed')->default(DB::raw('CURRENT_TIMESTAMP'))->nullable(); }); diff --git a/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php b/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php similarity index 82% rename from database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php rename to database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php index 15fafb168..0d0f4e203 100644 --- a/database/migrations/2022_07_21_234527_add_cancelation_to_servers_table.php +++ b/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php @@ -13,6 +13,11 @@ class AddCancelationToServersTable extends Migration */ public function up() { + // User already has installed the addon before + if (Schema::hasColumn("servers", "cancelled")) { + return; + } + Schema::table('servers', function (Blueprint $table) { $table->dateTime('cancelled')->nullable(); }); diff --git a/lang/en.json b/lang/en.json index 4db852063..90f23aae0 100644 --- a/lang/en.json +++ b/lang/en.json @@ -600,5 +600,4 @@ "Billing period": "Billing period", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", "Caution": "Caution" - "hu": "Hungarian" } From ad5ff407614cac9b1c037e1ceb0f6b8bd941ecf0 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Mon, 8 May 2023 11:26:47 +0200 Subject: [PATCH 147/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Old=20settings=20?= =?UTF-8?q?relict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 16 ++++++++-------- themes/default/views/servers/index.blade.php | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 23d032bff..10917ee00 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -48,7 +48,7 @@ public function index(GeneralSettings $general_settings, PterodactylSettings $pt //Get server infos from ptero $serverAttributes = $this->pterodactyl->getServerAttributes($server->pterodactyl_id); - if (! $serverAttributes) { + if (!$serverAttributes) { continue; } $serverRelationships = $serverAttributes['relationships']; @@ -90,7 +90,7 @@ public function create(UserSettings $user_settings, ServerSettings $server_setti { $this->checkPermission(self::CREATE_PERMISSION); - $validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings); + $validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings, $general_settings); if (!is_null($validate_configuration)) { return $validate_configuration; @@ -132,7 +132,7 @@ public function create(UserSettings $user_settings, ServerSettings $server_setti /** * @return null|RedirectResponse */ - private function validateConfigurationRules(UserSettings $user_settings, ServerSettings $server_settings) + private function validateConfigurationRules(UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $generalSettings) { //limit validation if (Auth::user()->servers()->count() >= Auth::user()->server_limit) { @@ -157,7 +157,7 @@ private function validateConfigurationRules(UserSettings $user_settings, ServerS if (Auth::user()->credits < ($product->minimum_credits == -1 ? $user_settings->min_credits_to_make_server : $product->minimum_credits)) { - return redirect()->route('servers.index')->with('error', 'You do not have the required amount of '.CREDITS_DISPLAY_NAME.' to use this product!'); + return redirect()->route('servers.index')->with('error', 'You do not have the required amount of ' . $generalSettings->credits_display_name . ' to use this product!'); } } @@ -180,12 +180,12 @@ private function validateConfigurationRules(UserSettings $user_settings, ServerS } /** Store a newly created resource in storage. */ - public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings) + public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $generalSettings) { /** @var Node $node */ /** @var Egg $egg */ /** @var Product $product */ - $validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings); + $validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings, $generalSettings); if (!is_null($validate_configuration)) { return $validate_configuration; @@ -211,7 +211,7 @@ public function store(Request $request, UserSettings $user_settings, ServerSetti //get free allocation ID $allocationId = $this->pterodactyl->getFreeAllocationId($node); - if (! $allocationId) { + if (!$allocationId) { return $this->noAllocationsError($server); } @@ -338,7 +338,7 @@ public function upgrade(Server $server, Request $request) if ($server->user_id != Auth::user()->id) { return redirect()->route('servers.index'); } - if (! isset($request->product_upgrade)) { + if (!isset($request->product_upgrade)) { return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('this product is the only one')); } $user = Auth::user(); diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index 74dc6378e..83b39354c 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -174,7 +174,7 @@ class="fas fa-info-circle"></i> <div class="col-4"> {{ __('Price') }}: <span class="text-muted"> - ({{ CREDITS_DISPLAY_NAME }}) + ({{ $credits_display_name }}) </span> </div> <div class="col-8 text-center"> From da338aacdcc8d11d48537b561c0750ff8ead3dba Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Mon, 8 May 2023 11:35:28 +0200 Subject: [PATCH 148/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Old=20settings=20?= =?UTF-8?q?relict=20the=20second?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/servers/index.blade.php | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index 83b39354c..e700b15d6 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -204,19 +204,17 @@ class="fas fa-info-circle"></i> </div> <div class="card-footer text-center"> - <a href="{{ config('SETTINGS::SYSTEM:PTERODACTYL:URL') }}/server/{{ $server->identifier }}" + <a href="{{ $pterodactyl_url }}/server/{{ $server->identifier }}" target="__blank" class="btn btn-info text-center float-left ml-2" data-toggle="tooltip" data-placement="bottom" title="{{ __('Manage Server') }}"> <i class="fas fa-tools mx-2"></i> </a> - @if(config("SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN")) <a href="{{ route('servers.show', ['server' => $server->id])}}" class="btn btn-info text-center mr-3" data-toggle="tooltip" data-placement="bottom" title="{{ __('Server Settings') }}"> <i class="fas fa-cog mx-2"></i> </a> - @endif <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" {{ $server->suspended || $server->cancelled ? "disabled" : "" }} @@ -260,7 +258,14 @@ class="btn btn-danger text-center float-right mr-2" } }).then(() => { window.location.reload(); - }); + }).catch((error) => { + Swal.fire({ + title: "{{ __('Error') }}", + text: "{{ __('Something went wrong, please try again later.') }}", + icon: 'error', + confirmButtonColor: '#d9534f', + }) + }) return } }) @@ -286,7 +291,14 @@ class="btn btn-danger text-center float-right mr-2" } }).then(() => { window.location.reload(); - }); + }).catch((error) => { + Swal.fire({ + title: "{{ __('Error') }}", + text: "{{ __('Something went wrong, please try again later.') }}", + icon: 'error', + confirmButtonColor: '#d9534f', + }) + }) return } }); From b1aaaeb329b59b628894f1837dee344dff9adc43 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Mon, 8 May 2023 11:39:21 +0200 Subject: [PATCH 149/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20add=20missing=20c?= =?UTF-8?q?ancel=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/ServerController.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 10917ee00..6324f870e 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -276,7 +276,13 @@ public function destroy(Server $server) /** Cancel Server */ public function cancel(Server $server) { + if ($server->user_id != Auth::user()->id) { + return back()->with('error', __('This is not your Server!')); + } try { + $server->update([ + 'cancelled' => now(), + ]); return redirect()->route('servers.index')->with('success', __('Server cancelled')); } catch (Exception $e) { return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to cancel the server"') . $e->getMessage() . '"'); @@ -286,8 +292,6 @@ public function cancel(Server $server) /** Show Server Settings */ public function show(Server $server, ServerSettings $server_settings, GeneralSettings $general_settings) { - - if ($server->user_id != Auth::user()->id) { return back()->with('error', __('This is not your Server!')); } From 6bba6123c602eef7110a4b8daa43a4730e4f9760 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 11:55:17 +0200 Subject: [PATCH 150/514] Update to new Settings --- app/Http/Controllers/Api/UserController.php | 5 +- routes/web.php | 4 +- .../BlueInfinity/views/layouts/app.blade.php | 2 +- .../BlueInfinity/views/layouts/main.blade.php | 240 ++++++++++++------ 4 files changed, 172 insertions(+), 79 deletions(-) diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php index 0dd445be3..726eb9e86 100644 --- a/app/Http/Controllers/Api/UserController.php +++ b/app/Http/Controllers/Api/UserController.php @@ -8,6 +8,7 @@ use App\Models\DiscordUser; use App\Models\User; use App\Notifications\ReferralNotification; +use App\Settings\UserSettings; use Carbon\Carbon; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Pagination\LengthAwarePaginator; @@ -257,7 +258,7 @@ protected function createReferralCode() /** * @throws ValidationException */ - public function store(Request $request) + public function store(Request $request, UserSettings $userSettings) { $request->validate([ 'name' => ['required', 'string', 'max:30', 'min:4', 'alpha_num', 'unique:users'], @@ -266,7 +267,7 @@ public function store(Request $request) ]); // Prevent the creation of new users via API if this is enabled. - if (! config('SETTINGS::SYSTEM:CREATION_OF_NEW_USERS', 'true')) { + if (! $userSettings->creation_enabled) { throw ValidationException::withMessages([ 'error' => 'The creation of new users has been blocked by the system administrator.', ]); diff --git a/routes/web.php b/routes/web.php index 6f682f7f0..8de0863e3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -75,7 +75,9 @@ Route::resource('notifications', NotificationController::class); Route::patch('/servers/cancel/{server}', [ServerController::class, 'cancel'])->name('servers.cancel'); Route::resource('servers', ServerController::class); - if (config('SETTINGS::SYSTEM:ENABLE_UPGRADE')) { + + $serverSettings = app(App\Settings\ServerSettings::class); + if ($serverSettings->enable_upgrade) { Route::post('servers/{server}/upgrade', [ServerController::class, 'upgrade'])->name('servers.upgrade'); } diff --git a/themes/BlueInfinity/views/layouts/app.blade.php b/themes/BlueInfinity/views/layouts/app.blade.php index 07050e684..376f1d920 100644 --- a/themes/BlueInfinity/views/layouts/app.blade.php +++ b/themes/BlueInfinity/views/layouts/app.blade.php @@ -27,7 +27,7 @@ <noscript> <link rel="stylesheet" href="{{ asset('plugins/fontawesome-free/css/all.min.css') }}"> </noscript> - @if (config('SETTINGS::RECAPTCHA:ENABLED') == 'true') + @if (app(App\Settings\GeneralSettings::class)->recaptcha_enabled) {!! htmlScriptTagJsApi() !!} @endif <link rel="stylesheet" href="{{ asset('themes/BlueInfinity/app.css') }}"> diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index aa4a7cf55..b6549a7a0 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -2,22 +2,26 @@ <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> + @php($website_settings = app(App\Settings\WebsiteSettings::class)) + @php($general_settings = app(App\Settings\GeneralSettings::class)) <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> - <meta content="{{ config('SETTINGS::SYSTEM:SEO_TITLE') }}" property="og:title"> - <meta content="{{ config('SETTINGS::SYSTEM:SEO_DESCRIPTION') }}" property="og:description"> - <meta content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}' property="og:image"> + <meta content="{{ $website_settings->seo_title }}" property="og:title"> + <meta content="{{ $website_settings->seo_description }}" property="og:description"> + <meta + content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}' + property="og:image"> <title>{{ config('app.name', 'Laravel') }}</title> <link rel="icon" href="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('favicon.ico') }}" type="image/x-icon"> - <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script> + <script src="{{ asset('plugins/alpinejs/3.12.0_cdn.min.js') }}" defer></script> {{-- <link rel="stylesheet" href="{{asset('css/adminlte.min.css')}}"> --}} - <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/bs4/dt-1.10.24/datatables.min.css" /> + <link rel="stylesheet" href="{{ asset('plugins/datatables/jquery.dataTables.min.css') }}"> {{-- summernote --}} <link rel="stylesheet" href="{{ asset('plugins/summernote/summernote-bs4.min.css') }}"> @@ -36,7 +40,7 @@ </noscript> <script src="{{ asset('js/app.js') }}"></script> <!-- tinymce --> - <script src={{ asset('plugins/tinymce/js/tinymce/tinymce.min.js') }}></script> + <script src="{{ asset('plugins/tinymce/js/tinymce/tinymce.min.js') }}"></script> <link rel="stylesheet" href="{{ asset('themes/BlueInfinity/app.css') }}"> </head> @@ -54,15 +58,16 @@ class="fas fa-bars"></i></a> <a href="{{ route('home') }}" class="nav-link"><i class="fas fa-home mr-2"></i>{{ __('Home') }}</a> </li> - @if (config('SETTINGS::DISCORD:INVITE_URL')) + @if (!empty($discord_settings->invite_url)) <li class="nav-item d-none d-sm-inline-block"> - <a href="{{ config('SETTINGS::DISCORD:INVITE_URL') }}" class="nav-link" target="__blank"><i + <a href="{{ $discord_settings->invite_url }}" class="nav-link" target="__blank"><i class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> </li> @endif <!-- Language Selection --> - @if (config('SETTINGS::LOCALE:CLIENTS_CAN_CHANGE') == 'true') + @php($locale_settings = app(App\Settings\LocaleSettings::class)) + @if ($locale_settings->clients_can_change) <li class="nav-item dropdown"> <a class="nav-link" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> @@ -74,7 +79,7 @@ class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> aria-labelledby="changeLocale"> <form method="post" action="{{ route('changeLocale') }}" class="nav-item text-center"> @csrf - @foreach (explode(',', config('SETTINGS::LOCALE:AVAILABLE')) as $key) + @foreach (explode(',', $locale_settings->available) as $key) <button class="dropdown-item" name="inputLocale" value="{{ $key }}"> {{ __($key) }} </button> @@ -85,10 +90,10 @@ class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> </li> <!-- End Language Selection --> @endif - @foreach($useful_links as $link) + @foreach ($useful_links as $link) <li class="nav-item d-none d-sm-inline-block"> <a href="{{ $link->link }}" class="nav-link" target="__blank"><i - class="{{$link->icon}}"></i> {{ $link->title }}</a> + class="{{ $link->icon }}"></i> {{ $link->title }}</a> </li> @endforeach </ul> @@ -230,11 +235,7 @@ class="nav-link @if (Request::routeIs('servers.*')) active @endif"> </a> </li> - @if (env('APP_ENV') == 'local' || - (config('SETTINGS::PAYMENTS:PAYPAL:SECRET') && config('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID')) || - (config('SETTINGS::PAYMENTS:STRIPE:SECRET') && - config('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET') && - config('SETTINGS::PAYMENTS:STRIPE:METHODS'))) + @if (env('APP_ENV') == 'local' || $general_settings->store_enabled) <li class="nav-item"> <a href="{{ route('store.index') }}" class="nav-link @if (Request::routeIs('store.*') || Request::routeIs('checkout')) active @endif"> @@ -243,19 +244,35 @@ class="nav-link @if (Request::routeIs('store.*') || Request::routeIs('checkout') </a> </li> @endif - @if (config('SETTINGS::TICKET:ENABLED')) + @php($ticket_enabled = app(App\Settings\TicketSettings::class)->enabled) + @if ($ticket_enabled) + @canany(["user.ticket.read", "user.ticket.write"]) + <li class="nav-item"> + <a href="{{ route('ticket.index') }}" + class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> + <i class="nav-icon fas fas fa-ticket-alt"></i> + <p>{{ __('Support Ticket') }}</p> + </a> + </li> + @endcanany + @endif + + <!-- lol how do i make this shorter? --> + @canany(['settings.discord.read','settings.discord.write','settings.general.read','settings.general.write','settings.invoice.read','settings.invoice.write','settings.locale.read','settings.locale.write','settings.mail.read','settings.mail.write','settings.pterodactyl.read','settings.pterodactyl.write','settings.referral.read','settings.referral.write','settings.server.read','settings.server.write','settings.ticket.read','settings.ticket.write','settings.user.read','settings.user.write','settings.website.read','settings.website.write','settings.paypal.read','settings.paypal.write','settings.stripe.read','settings.stripe.write','settings.mollie.read','settings.mollie.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) + <li class="nav-header">{{ __('Administration') }}</li> + @endcanany + + @canany(['admin.overview.read','admin.overview.sync']) <li class="nav-item"> - <a href="{{ route('ticket.index') }}" - class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> - <i class="nav-icon fas fas fa-ticket-alt"></i> - <p>{{ __('Support Ticket') }}</p> + <a href="{{ route('admin.overview.index') }}" + class="nav-link @if (Request::routeIs('admin.overview.*')) active @endif"> + <i class="nav-icon fa fa-home"></i> + <p>{{ __('Overview') }}</p> </a> </li> - @endif - - @if ((Auth::user()->hasRole("Admin") || Auth::user()->role == 'moderator') && config('SETTINGS::TICKET:ENABLED')) - <li class="nav-header">{{ __('Moderation') }}</li> + @endcanany + @canany(['admin.ticket.read','admin.tickets.write']) <li class="nav-item"> <a href="{{ route('admin.ticket.index') }}" class="nav-link @if (Request::routeIs('admin.ticket.index')) active @endif"> @@ -263,6 +280,9 @@ class="nav-link @if (Request::routeIs('admin.ticket.index')) active @endif"> <p>{{ __('Ticket List') }}</p> </a> </li> + @endcanany + + @canany(['admin.ticket_blacklist.read','admin.ticket_blacklist.write']) <li class="nav-item"> <a href="{{ route('admin.ticket.blacklist') }}" class="nav-link @if (Request::routeIs('admin.ticket.blacklist')) active @endif"> @@ -270,20 +290,46 @@ class="nav-link @if (Request::routeIs('admin.ticket.blacklist')) active @endif"> <p>{{ __('Ticket Blacklist') }}</p> </a> </li> - @endif - - @if (Auth::user()->hasRole("Admin")) - <li class="nav-header">{{ __('Administration') }}</li> + @endcanany + @canany(['admin.roles.read','admin.roles.write']) <li class="nav-item"> - <a href="{{ route('admin.overview.index') }}" - class="nav-link @if (Request::routeIs('admin.overview.*')) active @endif"> - <i class="nav-icon fa fa-home"></i> - <p>{{ __('Overview') }}</p> + <a href="{{ route('admin.roles.index') }}" + class="nav-link @if (Request::routeIs('admin.roles.*')) active @endif"> + <i class="nav-icon fa fa-user-check"></i> + <p>{{ __('Role Management') }}</p> </a> </li> - - + @endcanany + + @canany(['settings.discord.read', + 'settings.discord.write', + 'settings.general.read', + 'settings.general.write', + 'settings.invoice.read', + 'settings.invoice.write', + 'settings.locale.read', + 'settings.locale.write', + 'settings.mail.read', + 'settings.mail.write', + 'settings.pterodactyl.read', + 'settings.pterodactyl.write', + 'settings.referral.read', + 'settings.referral.write', + 'settings.server.read', + 'settings.server.write', + 'settings.ticket.read', + 'settings.ticket.write', + 'settings.user.read', + 'settings.user.write', + 'settings.website.read', + 'settings.website.write', + 'settings.paypal.read', + 'settings.paypal.write', + 'settings.stripe.read', + 'settings.stripe.write', + 'settings.mollie.read', + 'settings.mollie.write',]) <li class="nav-item"> <a href="{{ route('admin.settings.index') }}" class="nav-link @if (Request::routeIs('admin.settings.*')) active @endif"> @@ -291,7 +337,9 @@ class="nav-link @if (Request::routeIs('admin.settings.*')) active @endif"> <p>{{ __('Settings') }}</p> </a> </li> + @endcanany + @canany(['admin.api.read','admin.api.write']) <li class="nav-item"> <a href="{{ route('admin.api.index') }}" class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> @@ -299,9 +347,40 @@ class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> <p>{{ __('Application API') }}</p> </a> </li> - + @endcanany + + <!-- good fuck do i shorten this lol --> + @canany(['admin.users.read', + 'admin.users.write', + 'admin.users.suspend', + 'admin.users.write.credits', + 'admin.users.write.username', + 'admin.users.write.password', + 'admin.users.write.role', + 'admin.users.write.referal', + 'admin.users.write.pterodactyl','admin.servers.read', + 'admin.servers.write', + 'admin.servers.suspend', + 'admin.servers.write.owner', + 'admin.servers.write.identifier', + 'admin.servers.delete','admin.products.read', + 'admin.products.create', + 'admin.products.edit', + 'admin.products.delete',]) <li class="nav-header">{{ __('Management') }}</li> + @endcanany + + + @canany(['admin.users.read', + 'admin.users.write', + 'admin.users.suspend', + 'admin.users.write.credits', + 'admin.users.write.username', + 'admin.users.write.password', + 'admin.users.write.role', + 'admin.users.write.referal', + 'admin.users.write.pterodactyl']) <li class="nav-item"> <a href="{{ route('admin.users.index') }}" class="nav-link @if (Request::routeIs('admin.users.*')) active @endif"> @@ -309,7 +388,13 @@ class="nav-link @if (Request::routeIs('admin.users.*')) active @endif"> <p>{{ __('Users') }}</p> </a> </li> - + @endcanany + @canany(['admin.servers.read', + 'admin.servers.write', + 'admin.servers.suspend', + 'admin.servers.write.owner', + 'admin.servers.write.identifier', + 'admin.servers.delete']) <li class="nav-item"> <a href="{{ route('admin.servers.index') }}" class="nav-link @if (Request::routeIs('admin.servers.*')) active @endif"> @@ -317,7 +402,11 @@ class="nav-link @if (Request::routeIs('admin.servers.*')) active @endif"> <p>{{ __('Servers') }}</p> </a> </li> - + @endcanany + @canany(['admin.products.read', + 'admin.products.create', + 'admin.products.edit', + 'admin.products.delete']) <li class="nav-item"> <a href="{{ route('admin.products.index') }}" class="nav-link @if (Request::routeIs('admin.products.*')) active @endif"> @@ -325,7 +414,8 @@ class="nav-link @if (Request::routeIs('admin.products.*')) active @endif"> <p>{{ __('Products') }}</p> </a> </li> - + @endcanany + @canany(['admin.store.read','admin.store.write','admin.store.disable']) <li class="nav-item"> <a href="{{ route('admin.store.index') }}" class="nav-link @if (Request::routeIs('admin.store.*')) active @endif"> @@ -333,7 +423,8 @@ class="nav-link @if (Request::routeIs('admin.store.*')) active @endif"> <p>{{ __('Store') }}</p> </a> </li> - + @endcanany + @canany(["admin.voucher.read","admin.voucher.read"]) <li class="nav-item"> <a href="{{ route('admin.vouchers.index') }}" class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif"> @@ -341,7 +432,8 @@ class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif"> <p>{{ __('Vouchers') }}</p> </a> </li> - + @endcanany + @canany(["admin.partners.read","admin.partners.read"]) <li class="nav-item"> <a href="{{ route('admin.partners.index') }}" class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif"> @@ -349,28 +441,13 @@ class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif"> <p>{{ __('Partners') }}</p> </a> </li> + @endcanany - {{-- <li class="nav-header">Pterodactyl</li> --}} - - {{-- <li class="nav-item"> --}} - {{-- <a href="{{route('admin.nodes.index')}}" --}} - {{-- class="nav-link @if (Request::routeIs('admin.nodes.*')) active @endif"> --}} - {{-- <i class="nav-icon fas fa-sitemap"></i> --}} - {{-- <p>Nodes</p> --}} - {{-- </a> --}} - {{-- </li> --}} - - {{-- <li class="nav-item"> --}} - {{-- <a href="{{route('admin.nests.index')}}" --}} - {{-- class="nav-link @if (Request::routeIs('admin.nests.*')) active @endif"> --}} - {{-- <i class="nav-icon fas fa-th-large"></i> --}} - {{-- <p>Nests</p> --}} - {{-- </a> --}} - {{-- </li> --}} - - + @canany(["admin.useful_links.read","admin.legal.read"]) <li class="nav-header">{{ __('Other') }}</li> + @endcanany + @canany(["admin.useful_links.read","admin.useful_links.write"]) <li class="nav-item"> <a href="{{ route('admin.usefullinks.index') }}" class="nav-link @if (Request::routeIs('admin.usefullinks.*')) active @endif"> @@ -378,7 +455,9 @@ class="nav-link @if (Request::routeIs('admin.usefullinks.*')) active @endif"> <p>{{ __('Useful Links') }}</p> </a> </li> + @endcanany + @canany(["admin.legal.read","admin.legal.write"]) <li class="nav-item"> <a href="{{ route('admin.legal.index') }}" class="nav-link @if (Request::routeIs('admin.legal.*')) active @endif"> @@ -386,9 +465,14 @@ class="nav-link @if (Request::routeIs('admin.legal.*')) active @endif"> <p>{{ __('Legal Sites') }}</p> </a> </li> + @endcanany + + @canany(["admin.payments.read","admin.logs.read"]) <li class="nav-header">{{ __('Logs') }}</li> + @endcanany + @can("admin.payments.read") <li class="nav-item"> <a href="{{ route('admin.payments.index') }}" class="nav-link @if (Request::routeIs('admin.payments.*')) active @endif"> @@ -399,7 +483,9 @@ class="badge badge-success right">{{ \App\Models\Payment::count() }}</span> </p> </a> </li> + @endcan + @can("admin.logs.read") <li class="nav-item"> <a href="{{ route('admin.activitylogs.index') }}" class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> @@ -407,7 +493,8 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <p>{{ __('Activity Logs') }}</p> </a> </li> - @endif + @endcan + </ul> </nav> @@ -420,17 +507,19 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <div class="content-wrapper"> - @if (!Auth::user()->hasVerifiedEmail()) + <!-- + @if (!Auth::user()->hasVerifiedEmail()) @if (Auth::user()->created_at->diffInHours(now(), false) > 1) <div class="alert alert-warning p-2 m-2"> <h5><i class="icon fas fa-exclamation-circle"></i> {{ __('Warning!') }}</h5> - {{ __('You have not yet verified your email address') }} <a class="text-primary" - href="{{ route('verification.send') }}">{{ __('Click here to resend verification email') }}</a> - <br> - {{ __('Please contact support If you didnt receive your verification email.') }} + {{ __('You have not yet verified your email address') }} <a class="text-primary" + href="{{ route('verification.send') }}">{{ __('Click here to resend verification email') }}</a> + <br> + {{ __('Please contact support If you didnt receive your verification email.') }} </div> - @endif +@endif @endif + --> @yield('content') @@ -441,21 +530,22 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <strong>Copyright © 2021-{{ date('Y') }} <a href="{{ url('/') }}">{{ env('APP_NAME', 'Laravel') }}</a>.</strong> All rights - reserved. Powered by <a href="https://CtrlPanel.gg">ControlPanel</a>. | Theme by <a href="https://2icecube.de/cpgg">2IceCube</a> + reserved. Powered by <a href="https://CtrlPanel.gg">CtrlPanel</a>. | Theme by <a href="https://2icecube.de/cpgg">2IceCube</a> @if (!str_contains(config('BRANCHNAME'), 'main') && !str_contains(config('BRANCHNAME'), 'unknown')) Version <b>{{ config('app')['version'] }} - {{ config('BRANCHNAME') }}</b> @endif {{-- Show imprint and privacy link --}} <div class="float-right d-none d-sm-inline-block"> - @if (config('SETTINGS::SYSTEM:SHOW_IMPRINT') == "true") + @if ($website_settings->show_imprint) <a target="_blank" href="{{ route('imprint') }}"><strong>{{ __('Imprint') }}</strong></a> | @endif - @if (config('SETTINGS::SYSTEM:SHOW_PRIVACY') == "true") + @if ($website_settings->show_privacy) <a target="_blank" href="{{ route('privacy') }}"><strong>{{ __('Privacy') }}</strong></a> @endif - @if (config('SETTINGS::SYSTEM:SHOW_TOS') == "true") - | <a target="_blank" href="{{ route('tos') }}"><strong>{{ __('Terms of Service') }}</strong></a> + @if ($website_settings->show_tos) + | <a target="_blank" + href="{{ route('tos') }}"><strong>{{ __('Terms of Service') }}</strong></a> @endif </div> </footer> @@ -469,9 +559,9 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <!-- ./wrapper --> <!-- Scripts --> -<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10.14.1/dist/sweetalert2.all.min.js"></script> +<script src="{{ asset('plugins/sweetalert2/sweetalert2.all.min.js') }}"></script> -<script type="text/javascript" src="https://cdn.datatables.net/v/bs4/dt-1.10.24/datatables.min.js"></script> +<script src="{{ asset('plugins/datatables/jquery.dataTables.min.js') }}"></script> <!-- Summernote --> <script src="{{ asset('plugins/summernote/summernote-bs4.min.js') }}"></script> <!-- select2 --> From 34287d00144310d64cfac765b171b2987a3a5efe Mon Sep 17 00:00:00 2001 From: Dennis <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 12:03:08 +0200 Subject: [PATCH 151/514] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e6591b4d..1bbc5152f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ### Features -- PayPal Integration -- Stripe Integration +- PayPal, Stripe and Mollie Integration +- Hourly, Weekely, Monthly, Quarterly and Annual billing Cycles - Referral System - Partner System - Ticket System From fb6b2ba2efee36cf4575d72d083ace121ee39b93 Mon Sep 17 00:00:00 2001 From: Dennis <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 12:05:23 +0200 Subject: [PATCH 152/514] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bbc5152f..c121ff78a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@    [](https://crowdin.com/project/controlpanelgg)    ## About -CtrlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that credits users hourly for each server they have and suspends them if they run out of credits. +CtrlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that charges users depending on the billing cycle you chose for each server they have and suspends them if they run out of credits. This dashboard offers an easy to use and free billing solution for all starting and experienced hosting providers. This dashboard has many customisation options and added discord Oauth verification to offer a solid link between your discord server and your dashboard. You can check our [Demo here](https://demo.CtrlPanel.gg "Demo"). From 9804149beb183ea8793b1e19f5f693f2cda2ecfd Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 12:07:23 +0200 Subject: [PATCH 153/514] oom killer frontend --- themes/default/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index e700b15d6..dd417eac2 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -126,7 +126,7 @@ class="fas fa-info-circle"></i> <span>{{ $server->product->name }} </span> <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/> {{ __('Billing Period') }}: {{$server->product->billing_period}}" + data-content="{{ __('CPU') }}: {{ $server->product->cpu / 100 }} {{ __('vCores') }} <br/>{{ __('RAM') }}: {{ $server->product->memory }} MB <br/>{{ __('Disk') }}: {{ $server->product->disk }} MB <br/>{{ __('Backups') }}: {{ $server->product->backups }} <br/> {{ __('MySQL Databases') }}: {{ $server->product->databases }} <br/> {{ __('Allocations') }}: {{ $server->product->allocations }} <br/>{{ __('OOM Killer') }}: {{ $server->product->oom_killer ? __("enabled") : __("disabled") }} <br/> {{ __('Billing Period') }}: {{$server->product->billing_period}}" class="fas fa-info-circle"></i> </div> </div> From 88ae8774e0c36de11d83d2a106e8f4904f5f09f1 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Mon, 8 May 2023 12:12:16 +0200 Subject: [PATCH 154/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20status=20to?= =?UTF-8?q?=20payments=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/admin/payments/index.blade.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/themes/default/views/admin/payments/index.blade.php b/themes/default/views/admin/payments/index.blade.php index cda9984cd..93ce751fa 100644 --- a/themes/default/views/admin/payments/index.blade.php +++ b/themes/default/views/admin/payments/index.blade.php @@ -47,6 +47,7 @@ class="btn btn-info">{{ __('Download all Invoices') }}</button></a> <th>{{ __('Total Price') }}</th> <th>{{ __('Payment ID') }}</th> <th>{{ __('Payment Method') }}</th> + <th>{{ __('Status') }}</th> <th>{{ __('Created at') }}</th> <th></th> </tr> @@ -85,6 +86,7 @@ class="btn btn-info">{{ __('Download all Invoices') }}</button></a> {data: 'total_price'}, {data: 'payment_id'}, {data: 'payment_method'}, + {data: 'status'}, {data: 'created_at', type: 'num', render: {_: 'display', sort: 'raw'}}, {data: 'actions' , sortable : false}, ], From 98a894fd1eac2d0c6bd353fc14799a3827a31484 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 12:26:49 +0200 Subject: [PATCH 155/514] possible fix? --- database/settings/2023_02_04_181156_create_ticket_settings.php | 3 ++- database/settings/2023_05_07_195343_ticket_information.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/database/settings/2023_02_04_181156_create_ticket_settings.php b/database/settings/2023_02_04_181156_create_ticket_settings.php index 701ca14ae..4637cbe3c 100644 --- a/database/settings/2023_02_04_181156_create_ticket_settings.php +++ b/database/settings/2023_02_04_181156_create_ticket_settings.php @@ -10,7 +10,8 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('ticket.enabled', $table_exists ? $this->getOldValue('SETTINGS::TICKET:ENABLED') : 'all'); + $this->migrator->add('ticket.enabled', $table_exists ? $this->getOldValue('SETTINGS::TICKET:ENABLED') : 'true'); + $this->migrator->add('ticket.notify', $table_exists ? $this->getOldValue('SETTINGS::TICKET:NOTIFY') : 'all'); } public function down(): void diff --git a/database/settings/2023_05_07_195343_ticket_information.php b/database/settings/2023_05_07_195343_ticket_information.php index a4d89f25c..21d9aa67b 100644 --- a/database/settings/2023_05_07_195343_ticket_information.php +++ b/database/settings/2023_05_07_195343_ticket_information.php @@ -6,6 +6,7 @@ { public function up(): void { + $this->migrator->delete('ticket.notify'); $this->migrator->add('ticket.information', "Can't start your server? Need an additional port? Do you have any other questions? Let us know by opening a ticket."); } }; From 78dfe881d28dc2c02529662e5b36d4700d8803ab Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 12:35:52 +0200 Subject: [PATCH 156/514] Fix migration --- .../settings/2023_02_04_181156_create_ticket_settings.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/database/settings/2023_02_04_181156_create_ticket_settings.php b/database/settings/2023_02_04_181156_create_ticket_settings.php index 4637cbe3c..1e71ad3a8 100644 --- a/database/settings/2023_02_04_181156_create_ticket_settings.php +++ b/database/settings/2023_02_04_181156_create_ticket_settings.php @@ -58,11 +58,16 @@ public function getOldValue(string $key) $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); // Handle the old values to return without it being a string in all cases. + + if (is_null($old_value)) { + return ''; + } if ($old_value->type === "string" || $old_value->type === "text") { if (is_null($old_value->value)) { return ''; } + // Some values have the type string, but their values are boolean. if ($old_value->value === "false" || $old_value->value === "true") { return filter_var($old_value->value, FILTER_VALIDATE_BOOL); From 29b5b39d1773a42010befad70141d3a2c16efa08 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 13:53:21 +0200 Subject: [PATCH 157/514] fix general settings --- app/Settings/GeneralSettings.php | 14 +++++++------- .../default/views/admin/settings/index.blade.php | 5 +++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/Settings/GeneralSettings.php b/app/Settings/GeneralSettings.php index 8aa91e222..06a4819ee 100644 --- a/app/Settings/GeneralSettings.php +++ b/app/Settings/GeneralSettings.php @@ -8,13 +8,13 @@ class GeneralSettings extends Settings { public bool $store_enabled; public string $credits_display_name; - public bool $recaptcha_enabled; - public string $recaptcha_site_key; - public string $recaptcha_secret_key; - public string $phpmyadmin_url; - public bool $alert_enabled; + public ?bool $recaptcha_enabled; + public ?string $recaptcha_site_key; + public ?string $recaptcha_secret_key; + public ?string $phpmyadmin_url; + public ?bool $alert_enabled; public string $alert_type; - public string $alert_message; + public ?string $alert_message; public string $theme; //public int $initial_user_role; wait for Roles & Permissions PR. @@ -41,7 +41,7 @@ public static function getValidations() 'phpmyadmin_url' => 'nullable|string', 'alert_enabled' => 'nullable|boolean', 'alert_type' => 'required|in:primary,secondary,success,danger,warning,info', - 'alert_message' => 'required|string', + 'alert_message' => 'nullable|string', 'theme' => 'required|in:default,BlueInfinity' // TODO: themes should be made/loaded dynamically ]; } diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index db4344b6e..28615dbee 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -121,10 +121,11 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> @case($value['type'] == 'select') <select id="{{ $key }}" class="custom-select w-100" name="{{ $key }}"> - @foreach ($value['options'] as $option) + + @foreach ($value['options'] as $option=>$display) <option value="{{ $option }}" {{ $value['value'] == $option ? 'selected' : '' }}> - {{ __($option) }} + {{ __($display) }} </option> @endforeach </select> From 6894e2db8435992030dd0407f2e0bbfa3f171515 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 14:01:02 +0200 Subject: [PATCH 158/514] ReImplement ThemeSwitcher --- app/Providers/AppServiceProvider.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8b82efad9..fb6e8f9ce 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,7 @@ use App\Extensions\PaymentGateways\PayPal\PayPalSettings; use App\Models\UsefulLink; +use App\Settings\GeneralSettings; use App\Settings\MailSettings; use Exception; use Illuminate\Pagination\Paginator; @@ -12,6 +13,7 @@ use Illuminate\Support\Facades\URL; use Illuminate\Support\Facades\Validator; use Illuminate\Support\ServiceProvider; +use Qirolab\Theme\Theme; class AppServiceProvider extends ServiceProvider @@ -87,6 +89,17 @@ public function boot() Log::error("Couldnt find useful_links. Probably the installation is not completet. " . $e); } + $generalSettings = $this->app->make(GeneralSettings::class); + if (!file_exists(base_path('themes') . "/" . $generalSettings->theme)) { + $generalSettings->theme = "default"; + } + + if ($generalSettings->theme && $generalSettings->theme !== config('theme.active')) { + Theme::set($generalSettings->theme, "default"); + } else { + Theme::set("default", "default"); + } + $settings = $this->app->make(MailSettings::class); $settings->setConfig(); From 3822c123ba2a8ae4204b8103029164f0005e38f8 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 14:20:42 +0200 Subject: [PATCH 159/514] Fix some Settings --- app/Settings/GeneralSettings.php | 2 +- app/Settings/ReferralSettings.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Settings/GeneralSettings.php b/app/Settings/GeneralSettings.php index 06a4819ee..5c4d14113 100644 --- a/app/Settings/GeneralSettings.php +++ b/app/Settings/GeneralSettings.php @@ -114,7 +114,7 @@ public static function getOptionInputData() 'description' => 'The type of alert to display.' ], 'alert_message' => [ - 'type' => 'string', + 'type' => 'textarea', 'label' => 'Alert Message', 'description' => 'The message to display in the alert.' ], diff --git a/app/Settings/ReferralSettings.php b/app/Settings/ReferralSettings.php index 47d086465..28e1a9edc 100644 --- a/app/Settings/ReferralSettings.php +++ b/app/Settings/ReferralSettings.php @@ -24,11 +24,10 @@ public static function group(): string public static function getValidations() { return [ - 'allowed' => 'required|in:Everyone,Clients', 'always_give_commission' => 'nullable|boolean', 'enabled' => 'nullable|boolean', 'reward' => 'nullable|numeric', - 'mode' => 'required|in:Commission,Sign-Up,Both', + 'mode' => 'required|in:comission,sign-up,both', 'percentage' => 'nullable|numeric', ]; } From 9dbd812fc2ee677e82889bddf84c86c0b2651d78 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 14:42:46 +0200 Subject: [PATCH 160/514] Fix Settings bool -> String --- app/Settings/GeneralSettings.php | 10 +++++----- app/Settings/InvoiceSettings.php | 2 +- app/Settings/LocaleSettings.php | 4 ++-- app/Settings/MailSettings.php | 2 +- app/Settings/ReferralSettings.php | 4 ++-- app/Settings/ServerSettings.php | 6 +++--- app/Settings/TicketSettings.php | 2 +- app/Settings/UserSettings.php | 8 ++++---- app/Settings/WebsiteSettings.php | 12 ++++++------ 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/Settings/GeneralSettings.php b/app/Settings/GeneralSettings.php index 5c4d14113..23c0c0f76 100644 --- a/app/Settings/GeneralSettings.php +++ b/app/Settings/GeneralSettings.php @@ -8,11 +8,11 @@ class GeneralSettings extends Settings { public bool $store_enabled; public string $credits_display_name; - public ?bool $recaptcha_enabled; + public bool $recaptcha_enabled; public ?string $recaptcha_site_key; public ?string $recaptcha_secret_key; public ?string $phpmyadmin_url; - public ?bool $alert_enabled; + public bool $alert_enabled; public string $alert_type; public ?string $alert_message; public string $theme; @@ -33,13 +33,13 @@ public static function group(): string public static function getValidations() { return [ - 'store_enabled' => 'boolean', + 'store_enabled' => 'nullable|string', 'credits_display_name' => 'required|string', - 'recaptcha_enabled' => 'nullable|boolean', + 'recaptcha_enabled' => 'nullable|string', 'recaptcha_site_key' => 'nullable|string', 'recaptcha_secret_key' => 'nullable|string', 'phpmyadmin_url' => 'nullable|string', - 'alert_enabled' => 'nullable|boolean', + 'alert_enabled' => 'nullable|string', 'alert_type' => 'required|in:primary,secondary,success,danger,warning,info', 'alert_message' => 'nullable|string', 'theme' => 'required|in:default,BlueInfinity' // TODO: themes should be made/loaded dynamically diff --git a/app/Settings/InvoiceSettings.php b/app/Settings/InvoiceSettings.php index 46dce9c54..c7b0068bf 100644 --- a/app/Settings/InvoiceSettings.php +++ b/app/Settings/InvoiceSettings.php @@ -33,7 +33,7 @@ public static function getValidations() 'company_phone' => 'nullable|string', 'company_vat' => 'nullable|string', 'company_website' => 'nullable|string', - 'enabled' => 'nullable|boolean', + 'enabled' => 'nullable|string', 'prefix' => 'nullable|string', ]; } diff --git a/app/Settings/LocaleSettings.php b/app/Settings/LocaleSettings.php index 7802f499b..61934bc17 100644 --- a/app/Settings/LocaleSettings.php +++ b/app/Settings/LocaleSettings.php @@ -25,10 +25,10 @@ public static function getValidations() { return [ 'available' => 'nullable|array', - 'clients_can_change' => 'nullable|boolean', + 'clients_can_change' => 'nullable|string', 'datatables' => 'nullable|string', 'default' => 'required|in:' . implode(',', config('app.available_locales')), - 'dynamic' => 'nullable|boolean', + 'dynamic' => 'nullable|string', ]; } diff --git a/app/Settings/MailSettings.php b/app/Settings/MailSettings.php index cd70a7fa4..8b37f632f 100644 --- a/app/Settings/MailSettings.php +++ b/app/Settings/MailSettings.php @@ -52,7 +52,7 @@ public static function getValidations() 'mail_from_address' => 'nullable|string', 'mail_from_name' => 'nullable|string', 'mail_mailer' => 'nullable|string', - 'mail_enabled' => 'nullable|boolean', + 'mail_enabled' => 'nullable|string', ]; } diff --git a/app/Settings/ReferralSettings.php b/app/Settings/ReferralSettings.php index 28e1a9edc..f56b71883 100644 --- a/app/Settings/ReferralSettings.php +++ b/app/Settings/ReferralSettings.php @@ -24,8 +24,8 @@ public static function group(): string public static function getValidations() { return [ - 'always_give_commission' => 'nullable|boolean', - 'enabled' => 'nullable|boolean', + 'always_give_commission' => 'nullable|string', + 'enabled' => 'nullable|string', 'reward' => 'nullable|numeric', 'mode' => 'required|in:comission,sign-up,both', 'percentage' => 'nullable|numeric', diff --git a/app/Settings/ServerSettings.php b/app/Settings/ServerSettings.php index ee43e6673..f56418765 100644 --- a/app/Settings/ServerSettings.php +++ b/app/Settings/ServerSettings.php @@ -24,9 +24,9 @@ public static function getValidations() { return [ 'allocation_limit' => 'required|integer|min:0', - 'creation_enabled' => 'nullable|boolean', - 'enable_upgrade' => 'nullable|boolean', - 'charge_first_hour' => 'nullable|boolean', + 'creation_enabled' => 'nullable|string', + 'enable_upgrade' => 'nullable|string', + 'charge_first_hour' => 'nullable|string', ]; } diff --git a/app/Settings/TicketSettings.php b/app/Settings/TicketSettings.php index cd621b173..89eec7270 100644 --- a/app/Settings/TicketSettings.php +++ b/app/Settings/TicketSettings.php @@ -21,7 +21,7 @@ public static function group(): string public static function getValidations() { return [ - 'enabled' => 'nullable|boolean', + 'enabled' => 'nullable|string', 'information' => 'nullable|string', ]; } diff --git a/app/Settings/UserSettings.php b/app/Settings/UserSettings.php index ec34f678e..5c1b65dba 100644 --- a/app/Settings/UserSettings.php +++ b/app/Settings/UserSettings.php @@ -33,16 +33,16 @@ public static function getValidations() return [ 'credits_reward_after_verify_discord' => 'required|numeric', 'credits_reward_after_verify_email' => 'required|numeric', - 'force_discord_verification' => 'nullable|boolean', - 'force_email_verification' => 'nullable|boolean', + 'force_discord_verification' => 'nullable|string', + 'force_email_verification' => 'nullable|string', 'initial_credits' => 'required|numeric', 'initial_server_limit' => 'required|numeric', 'min_credits_to_make_server' => 'required|numeric', 'server_limit_after_irl_purchase' => 'required|numeric', 'server_limit_after_verify_discord' => 'required|numeric', 'server_limit_after_verify_email' => 'required|numeric', - 'register_ip_check' => 'nullable|boolean', - 'creation_enabled' => 'nullable|boolean', + 'register_ip_check' => 'nullable|string', + 'creation_enabled' => 'nullable|string', ]; } diff --git a/app/Settings/WebsiteSettings.php b/app/Settings/WebsiteSettings.php index e557fd937..e6607c210 100644 --- a/app/Settings/WebsiteSettings.php +++ b/app/Settings/WebsiteSettings.php @@ -31,13 +31,13 @@ public static function group(): string public static function getValidations() { return [ - 'motd_enabled' => 'nullable|boolean', + 'motd_enabled' => 'nullable|string', 'motd_message' => 'nullable|string', - 'show_imprint' => 'nullable|boolean', - 'show_privacy' => 'nullable|boolean', - 'show_tos' => 'nullable|boolean', - 'useful_links_enabled' => 'nullable|boolean', - 'enable_login_logo' => 'nullable|boolean', + 'show_imprint' => 'nullable|string', + 'show_privacy' => 'nullable|string', + 'show_tos' => 'nullable|string', + 'useful_links_enabled' => 'nullable|string', + 'enable_login_logo' => 'nullable|string', 'seo_title' => 'nullable|string', 'seo_description' => 'nullable|string', ]; From 259a08b3bab75e687d5ff915f3f859c93267fc01 Mon Sep 17 00:00:00 2001 From: Dennis <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 14:48:07 +0200 Subject: [PATCH 161/514] fix div --- themes/default/views/home.blade.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/themes/default/views/home.blade.php b/themes/default/views/home.blade.php index 8993972c7..b3827bee1 100644 --- a/themes/default/views/home.blade.php +++ b/themes/default/views/home.blade.php @@ -150,9 +150,8 @@ class="info-box-text">{{ __('Out of Credits in', ['credits' => $general_settings </div> <!-- /.card-body --> </div> - + @endif </div> - @endif <div class="col-md-6"> <div class="card card-default"> From 166dccd3de5f9cd81e08252702b992b7ee0a6c92 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 21:16:06 +0200 Subject: [PATCH 162/514] Update web.php --- routes/web.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/routes/web.php b/routes/web.php index 8de0863e3..425c56135 100644 --- a/routes/web.php +++ b/routes/web.php @@ -76,9 +76,11 @@ Route::patch('/servers/cancel/{server}', [ServerController::class, 'cancel'])->name('servers.cancel'); Route::resource('servers', ServerController::class); - $serverSettings = app(App\Settings\ServerSettings::class); - if ($serverSettings->enable_upgrade) { - Route::post('servers/{server}/upgrade', [ServerController::class, 'upgrade'])->name('servers.upgrade'); + if (config('app.key')) { + $serverSettings = app(App\Settings\ServerSettings::class); + if ($serverSettings->enable_upgrade) { + Route::post('servers/{server}/upgrade', [ServerController::class, 'upgrade'])->name('servers.upgrade'); + } } Route::post('profile/selfdestruct', [ProfileController::class, 'selfDestroyUser'])->name('profile.selfDestroyUser'); From 6f74a7379f2fbfa0623688a6cc82f2b00ca75537 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 23:00:29 +0200 Subject: [PATCH 163/514] Show referredBy on User --- app/Models/User.php | 14 +++++++++++++ .../default/views/admin/users/show.blade.php | 21 ++++++++++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index c72e693c4..da4a3a7f9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\DB; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\CausesActivity; use Spatie\Activitylog\Traits\LogsActivity; @@ -97,6 +98,8 @@ public function __construct() $ptero_settings = new PterodactylSettings(); $this->pterodactyl = new PterodactylClient($ptero_settings); + + } public static function boot() @@ -285,6 +288,17 @@ public function reVerifyEmail() ])->save(); } + public function referredBy(){ + $referee = DB::table('user_referrals')->where("registered_user_id",$this->id)->first(); + + if($referee){ + $referee = User::where("id",$referee->referral_id)->firstOrFail(); + return $referee; + } + return Null; + + } + public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() diff --git a/themes/default/views/admin/users/show.blade.php b/themes/default/views/admin/users/show.blade.php index 3ab0d62ee..5656c307b 100644 --- a/themes/default/views/admin/users/show.blade.php +++ b/themes/default/views/admin/users/show.blade.php @@ -173,6 +173,20 @@ </div> </div> + + + <div class="col-lg-6"> + <div class="row"> + <div class="col-lg-4"> + <label>{{ __('IP') }}</label> + </div> + <div class="col-lg-8"> + <span style="max-width: 250px;" class="d-inline-block text-truncate"> + {{ $user->ip }} + </span> + </div> + </div> + </div> <div class="col-lg-6"> <div class="row"> <div class="col-lg-4"> @@ -185,21 +199,18 @@ </div> </div> </div> - - <div class="col-lg-6"> <div class="row"> <div class="col-lg-4"> - <label>{{ __('IP') }}</label> + <label>{{ __('Referred by') }}</label> </div> <div class="col-lg-8"> <span style="max-width: 250px;" class="d-inline-block text-truncate"> - {{ $user->ip }} + {{ $user->referredBy() != Null ? $user->referredBy()->name : "None" }} </span> </div> </div> </div> - <div class="col-lg-6"> <div class="row"> <div class="col-lg-4"> From f53aa0e8b0caf656a35821373da94d4a1661004f Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 8 May 2023 23:13:50 +0200 Subject: [PATCH 164/514] Fix Ptero UserID --- app/Http/Controllers/Auth/RegisterController.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 9f1e8019b..566d86fd5 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -142,7 +142,7 @@ protected function create(array $data) $user->syncRoles(4); $response = $this->pterodactyl->application->post('/application/users', [ - 'external_id' => $user->pterodactyl_id, + 'external_id' => null, 'username' => $user->name, 'email' => $user->email, 'first_name' => $user->name, @@ -151,6 +151,14 @@ protected function create(array $data) 'root_admin' => false, 'language' => 'en', ]); + + $user->update([ + 'pterodactyl_id' => $response->json()['attributes']['id'], + ]); + + + + if ($response->failed()) { $user->delete(); From 38db0a6ee0a028397c91c7842bcac6245dff37c6 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 9 May 2023 18:04:11 +0200 Subject: [PATCH 165/514] Fix Notifications --- app/Models/User.php | 5 +++-- app/Notifications/ReferralNotification.php | 13 +++++++++++-- app/Notifications/WelcomeMessage.php | 5 ++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index da4a3a7f9..6d14f585a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -106,8 +106,8 @@ public static function boot() { parent::boot(); - static::created(function (User $user, GeneralSettings $general_settings, UserSettings $user_settings) { - $user->notify(new WelcomeMessage($user, $general_settings, $user_settings)); + static::created(function (User $user) { + $user->notify(new WelcomeMessage($user)); }); static::deleting(function (User $user) { @@ -276,6 +276,7 @@ public function getVerifiedStatus() public function verifyEmail() { + $this->forceFill([ 'email_verified_at' => now(), ])->save(); diff --git a/app/Notifications/ReferralNotification.php b/app/Notifications/ReferralNotification.php index a1eaff0c8..f8acbcf52 100644 --- a/app/Notifications/ReferralNotification.php +++ b/app/Notifications/ReferralNotification.php @@ -19,6 +19,10 @@ class ReferralNotification extends Notification private $ref_user; + private $reward; + + private $credits_display_name; + /** * Create a new notification instance. * @@ -26,6 +30,11 @@ class ReferralNotification extends Notification */ public function __construct(int $user, int $ref_user) { + $general_settings= new GeneralSettings(); + $referral_settings = new ReferralSettings(); + + $this->credits_display_name = $general_settings->credits_display_name; + $this->reward = $referral_settings->reward; $this->user = User::findOrFail($user); $this->ref_user = User::findOrFail($ref_user); } @@ -47,12 +56,12 @@ public function via($notifiable) * @param mixed $notifiable * @return array */ - public function toArray($notifiable, GeneralSettings $general_settings, ReferralSettings $referral_settings) + public function toArray($notifiable) { return [ 'title' => __('Someone registered using your Code!'), 'content' => ' - <p>You received '. $referral_settings->reward . ' ' . $general_settings->credits_display_name . '</p> + <p>You received '. $this->reward . ' ' . $this->credits_display_name . '</p> <p>because ' . $this->ref_user->name . ' registered with your Referral-Code!</p> <p>Thank you very much for supporting us!.</p> <p>'.config('app.name', 'Laravel').'</p> diff --git a/app/Notifications/WelcomeMessage.php b/app/Notifications/WelcomeMessage.php index ac33e1c1b..7bef9f848 100644 --- a/app/Notifications/WelcomeMessage.php +++ b/app/Notifications/WelcomeMessage.php @@ -33,8 +33,11 @@ class WelcomeMessage extends Notification implements ShouldQueue * * @param User $user */ - public function __construct(User $user, GeneralSettings $general_settings, UserSettings $user_settings) + public function __construct(User $user) { + $general_settings= new GeneralSettings(); + $user_settings = new UserSettings(); + $this->user = $user; $this->credits_display_name = $general_settings->credits_display_name; $this->credits_reward_after_verify_discord = $user_settings->credits_reward_after_verify_discord; From b58040c45772b55078098d9ab4a78d59911c2fd3 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 9 May 2023 18:04:11 +0200 Subject: [PATCH 166/514] Fix Notifications --- app/Models/User.php | 5 +++-- app/Notifications/ReferralNotification.php | 13 +++++++++++-- app/Notifications/WelcomeMessage.php | 5 ++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index da4a3a7f9..6d14f585a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -106,8 +106,8 @@ public static function boot() { parent::boot(); - static::created(function (User $user, GeneralSettings $general_settings, UserSettings $user_settings) { - $user->notify(new WelcomeMessage($user, $general_settings, $user_settings)); + static::created(function (User $user) { + $user->notify(new WelcomeMessage($user)); }); static::deleting(function (User $user) { @@ -276,6 +276,7 @@ public function getVerifiedStatus() public function verifyEmail() { + $this->forceFill([ 'email_verified_at' => now(), ])->save(); diff --git a/app/Notifications/ReferralNotification.php b/app/Notifications/ReferralNotification.php index a1eaff0c8..f8acbcf52 100644 --- a/app/Notifications/ReferralNotification.php +++ b/app/Notifications/ReferralNotification.php @@ -19,6 +19,10 @@ class ReferralNotification extends Notification private $ref_user; + private $reward; + + private $credits_display_name; + /** * Create a new notification instance. * @@ -26,6 +30,11 @@ class ReferralNotification extends Notification */ public function __construct(int $user, int $ref_user) { + $general_settings= new GeneralSettings(); + $referral_settings = new ReferralSettings(); + + $this->credits_display_name = $general_settings->credits_display_name; + $this->reward = $referral_settings->reward; $this->user = User::findOrFail($user); $this->ref_user = User::findOrFail($ref_user); } @@ -47,12 +56,12 @@ public function via($notifiable) * @param mixed $notifiable * @return array */ - public function toArray($notifiable, GeneralSettings $general_settings, ReferralSettings $referral_settings) + public function toArray($notifiable) { return [ 'title' => __('Someone registered using your Code!'), 'content' => ' - <p>You received '. $referral_settings->reward . ' ' . $general_settings->credits_display_name . '</p> + <p>You received '. $this->reward . ' ' . $this->credits_display_name . '</p> <p>because ' . $this->ref_user->name . ' registered with your Referral-Code!</p> <p>Thank you very much for supporting us!.</p> <p>'.config('app.name', 'Laravel').'</p> diff --git a/app/Notifications/WelcomeMessage.php b/app/Notifications/WelcomeMessage.php index ac33e1c1b..7bef9f848 100644 --- a/app/Notifications/WelcomeMessage.php +++ b/app/Notifications/WelcomeMessage.php @@ -33,8 +33,11 @@ class WelcomeMessage extends Notification implements ShouldQueue * * @param User $user */ - public function __construct(User $user, GeneralSettings $general_settings, UserSettings $user_settings) + public function __construct(User $user) { + $general_settings= new GeneralSettings(); + $user_settings = new UserSettings(); + $this->user = $user; $this->credits_display_name = $general_settings->credits_display_name; $this->credits_reward_after_verify_discord = $user_settings->credits_reward_after_verify_discord; From 5cff1e54615f1e88870f7a7cbcba4a59e2614f8b Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 11 May 2023 09:32:13 +0200 Subject: [PATCH 167/514] Re-Implement Image uploading --- .../Controllers/Admin/SettingsController.php | 21 ++++++++ routes/web.php | 7 ++- .../views/admin/settings/index.blade.php | 54 +++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 7d1657f1d..47cc4207d 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -138,4 +138,25 @@ public function update(Request $request) return Redirect::to('admin/settings' . '#' . $category)->with('success', 'Settings updated successfully.'); } + + public function updateIcons(Request $request) + { + $request->validate([ + 'icon' => 'nullable|max:10000|mimes:jpg,png,jpeg', + 'logo' => 'nullable|max:10000|mimes:jpg,png,jpeg', + 'favicon' => 'nullable|max:10000|mimes:ico', + ]); + + if ($request->hasFile('icon')) { + $request->file('icon')->storeAs('public', 'icon.png'); + } + if ($request->hasFile('logo')) { + $request->file('logo')->storeAs('public', 'logo.png'); + } + if ($request->hasFile('favicon')) { + $request->file('favicon')->storeAs('public', 'favicon.ico'); + } + + return Redirect::to('admin/settings')->with('success', 'Icons updated successfully.'); + } } diff --git a/routes/web.php b/routes/web.php index 425c56135..5e09c048b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -169,13 +169,12 @@ Route::get('payments', [PaymentController::class, 'index'])->name('payments.index'); //settings - Route::get('settings/datatable', [SettingsController::class, 'datatable'])->name('settings.datatable'); - Route::patch('settings/updatevalue', [SettingsController::class, 'updatevalue'])->name('settings.updatevalue'); - Route::get('settings/checkPteroClientkey', [System::class, 'checkPteroClientkey'])->name('settings.checkPteroClientkey'); - Route::redirect('settings#system', 'system')->name('settings.system'); + Route::get('settings', [SettingsController::class, 'index'])->name('settings.index'); Route::post('settings', [SettingsController::class, 'update'])->name('settings.update'); + Route::post('settings/icons', [SettingsController::class, 'updateIcons'])->name('settings.updateIcons'); + //invoices Route::get('invoices/download-invoices', [InvoiceController::class, 'downloadAllInvoices'])->name('invoices.downloadAllInvoices'); diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 28615dbee..f3438dd2b 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -44,6 +44,17 @@ <nav class="mt-1"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" data-accordion="false"> + <li class="nav-item border-bottom-0"> + <a href="#icons" + class="nav-link" data-toggle="pill" + role="tab"> + <i + class="nav-icon fas fa-image"></i> + <p> + {{ __("Images / Icons") }} + </p> + </a> + </li> @foreach ($settings as $category => $options) @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) <li class="nav-item border-bottom-0"> @@ -66,6 +77,48 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> <!-- Content in $settings --> <div class="col-10 p-0"> <div class="tab-content ml-3" style="width: 100%;"> + <div container class="tab-pane fade container" + id="icons" role="tabpanel"> + + <form method="POST" enctype="multipart/form-data" class="mb-3" + action="{{ route('admin.settings.updateIcons') }}"> + @csrf + @method('POST') + <div class="row"> + <div class="card ml-5" style="width: 18rem;"> + <span class="h3 text-center">{{__("FavIcon")}} </span> + <div class="card-body"> + + </div> + <input type="file" accept="image/x-icon" class="form-control" name="favicon" + id="favicon"> + </div> + + <div class="card ml-5" style="width: 18rem;"> + <span class="h3 text-center">{{__("Icon")}} </span> + <img src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..." > + <div class="card-body"> + + </div> + <input type="file" accept="image/png,image/jpeg,image/jpg" class="form-control" + name="icon" id="icon"> + </div> + + <div class="card ml-5" style="width: 18rem;"> + <span class="h3 text-center">{{__("Login-page Logo")}} </span> + <img src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..." > + <div class="card-body"> + + </div> + <input type="file" accept="image/png,image/jpeg,image/jpg" class="form-control" + name="logo" id="logo"> + </div> + </div> + <div class="row"> + <button class="btn btn-primary ml-3 mt-3">{{ __('Save') }}</button> + </div> + </form> + </div> @foreach ($settings as $category => $options) @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) <div container class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" @@ -208,6 +261,7 @@ class="btn btn-secondary float-right ml-2">Reset</button> </div> </div> </div> + </div> </div> From a1d302701a545cd3efbcbc091215d24b1d42fa7d Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 11 May 2023 11:06:30 +0200 Subject: [PATCH 168/514] Collapseable extension settings --- .../views/admin/settings/index.blade.php | 310 ++++++++++-------- 1 file changed, 181 insertions(+), 129 deletions(-) diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index f3438dd2b..3c396a8ce 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -12,7 +12,8 @@ <ol class="breadcrumb float-sm-right"> <li class="breadcrumb-item"><a href="">{{ __('Dashboard') }}</a></li> <li class="breadcrumb-item"><a class="text-muted" - href="{{ route('admin.settings.index') }}">{{ __('Settings') }}</a></li> + href="{{ route('admin.settings.index') }}">{{ __('Settings') }}</a> + </li> </ol> </div> </div> @@ -24,7 +25,9 @@ <h4>{{ __('The installer is not locked!') }}</h4> <p>{{ __('please create a file called "install.lock" in your dashboard Root directory. Otherwise no settings will be loaded!') }} </p> - <a href="/install?step=7"><button class="btn btn-outline-danger">{{ __('or click here') }}</button></a> + <a href="/install?step=7"> + <button class="btn btn-outline-danger">{{ __('or click here') }}</button> + </a> </div> @endif @@ -56,21 +59,54 @@ class="nav-icon fas fa-image"></i> </a> </li> @foreach ($settings as $category => $options) - @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) - <li class="nav-item border-bottom-0"> - <a href="#{{ $category }}" - class="nav-link {{ $loop->first ? 'active' : '' }}" data-toggle="pill" - role="tab"> - <i - class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> - <p> - {{ $category }} - </p> - </a> - </li> - @endcanany + @if(!str_contains($options['settings_class'],"Extension")) + @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) + <li class="nav-item border-bottom-0"> + <a href="#{{ $category }}" + class="nav-link {{ $loop->first ? 'active' : '' }}" + data-toggle="pill" + role="tab"> + <i + class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> + <p> + {{ $category }} + </p> + </a> + </li> + @endcanany + @endif @endforeach </ul> + <i class="fa-solid fa-up-right-from-square"></i> + <button class="btn btn-outline-secondary" type="button" data-toggle="collapse" + data-target="#collapseExtensions" aria-expanded="false" + aria-controls="collapseExample"> + {{__("Extension Settings")}} + </button> + </p> + <div class="collapse" id="collapseExtensions"> + <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" + data-accordion="false"> + @foreach ($settings as $category => $options) + @if(str_contains($options['settings_class'],"Extension")) + + @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) + <li class="nav-item border-bottom-0"> + <a href="#{{ $category }}" + class="nav-link" + data-toggle="pill" + role="tab"> + <i class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> + <p> + {{ $category }} + </p> + </a> + </li> + @endcanany + @endif + @endforeach + </div> + </ul> </nav> </div> <!-- /.sidebar-menu --> @@ -85,32 +121,41 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> @csrf @method('POST') <div class="row"> - <div class="card ml-5" style="width: 18rem;"> - <span class="h3 text-center">{{__("FavIcon")}} </span> - <div class="card-body"> + <div class="card ml-5" style="width: 18rem;"> + <span class="h3 text-center">{{__("FavIcon")}} </span> + <div class="card-body"> - </div> - <input type="file" accept="image/x-icon" class="form-control" name="favicon" - id="favicon"> </div> + <input type="file" accept="image/x-icon" class="form-control" + name="favicon" + id="favicon"> + </div> <div class="card ml-5" style="width: 18rem;"> <span class="h3 text-center">{{__("Icon")}} </span> - <img src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..." > + <img + src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" + style="width:5vw;display: block; margin-left: auto;margin-right: auto" + class="card-img-top" alt="..."> <div class="card-body"> </div> - <input type="file" accept="image/png,image/jpeg,image/jpg" class="form-control" + <input type="file" accept="image/png,image/jpeg,image/jpg" + class="form-control" name="icon" id="icon"> </div> <div class="card ml-5" style="width: 18rem;"> <span class="h3 text-center">{{__("Login-page Logo")}} </span> - <img src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..." > + <img + src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" + style="width:5vw;display: block; margin-left: auto;margin-right: auto" + class="card-img-top" alt="..."> <div class="card-body"> </div> - <input type="file" accept="image/png,image/jpeg,image/jpg" class="form-control" + <input type="file" accept="image/png,image/jpeg,image/jpg" + class="form-control" name="logo" id="logo"> </div> </div> @@ -121,104 +166,110 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> </div> @foreach ($settings as $category => $options) @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) - <div container class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" - id="{{ $category }}" role="tabpanel"> - - <form action="{{ route('admin.settings.update') }}" method="POST"> - @csrf - @method('POST') - <input type="hidden" name="settings_class" - value="{{ $options['settings_class'] }}"> - <input type="hidden" name="category" value="{{ $category }}"> - - @foreach ($options as $key => $value) - @if ($key == 'category_icon' || $key == 'settings_class') - @continue - @endif - <div class="row"> - <div class="col-4 d-flex align-items-center"> - <label for="{{ $key }}">{{ $value['label'] }}</label> - </div> + <div + class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" + id="{{ $category }}" role="tabpanel"> + + <form action="{{ route('admin.settings.update') }}" method="POST"> + @csrf + @method('POST') + <input type="hidden" name="settings_class" + value="{{ $options['settings_class'] }}"> + <input type="hidden" name="category" value="{{ $category }}"> + + @foreach ($options as $key => $value) + @if ($key == 'category_icon' || $key == 'settings_class') + @continue + @endif + <div class="row"> + <div class="col-4 d-flex align-items-center"> + <label for="{{ $key }}">{{ $value['label'] }}</label> + </div> - <div class="col-8"> - <div class="custom-control mb-3 d-flex align-items-center"> - @if ($value['description']) - <i class="fas fa-info-circle mr-4" data-toggle="popover" - data-trigger="hover" data-placement="top" - data-html="true" - data-content="{{ $value['description'] }}"></i> - @else - <i class="fas fa-info-circle mr-4 invisible"></i> - @endif - - <div class="w-100"> - @switch($value) - @case($value['type'] == 'string') - <input type="text" class="form-control" - name="{{ $key }}" - value="{{ $value['value'] }}"> - @break - - @case($value['type'] == 'boolean') - <input type="checkbox" name="{{ $key }}" - value="{{ $value['value'] }}" - {{ $value['value'] ? 'checked' : '' }}> - @break - - @case($value['type'] == 'number') - <input type="number" class="form-control" - name="{{ $key }}" - value="{{ $value['value'] }}"> - @break - - @case($value['type'] == 'select') - <select id="{{ $key }}" - class="custom-select w-100" name="{{ $key }}"> - - @foreach ($value['options'] as $option=>$display) - <option value="{{ $option }}" - {{ $value['value'] == $option ? 'selected' : '' }}> - {{ __($display) }} - </option> - @endforeach - </select> - @break - - @case($value['type'] == 'multiselect') - <select id="{{ $key }}" - class="custom-select w-100" name="{{ $key }}[]" - multiple> - @foreach ($value['options'] as $option) - <option value="{{ $option }}" - {{ strpos($value['value'],$option) !== false ? 'selected' : '' }}> - {{ __($option) }} - </option> - @endforeach - </select> - @break - - @case($value['type'] == 'textarea') - <textarea class="form-control" name="{{ $key }}" rows="3">{{ $value['value'] }}</textarea> - @break - - @default - @endswitch - @error($key) + <div class="col-8"> + <div class="custom-control mb-3 d-flex align-items-center"> + @if ($value['description']) + <i class="fas fa-info-circle mr-4" + data-toggle="popover" + data-trigger="hover" data-placement="top" + data-html="true" + data-content="{{ $value['description'] }}"></i> + @else + <i class="fas fa-info-circle mr-4 invisible"></i> + @endif + + <div class="w-100"> + @switch($value) + @case($value['type'] == 'string') + <input type="text" class="form-control" + name="{{ $key }}" + value="{{ $value['value'] }}"> + @break + + @case($value['type'] == 'boolean') + <input type="checkbox" name="{{ $key }}" + value="{{ $value['value'] }}" + {{ $value['value'] ? 'checked' : '' }}> + @break + + @case($value['type'] == 'number') + <input type="number" class="form-control" + name="{{ $key }}" + value="{{ $value['value'] }}"> + @break + + @case($value['type'] == 'select') + <select id="{{ $key }}" + class="custom-select w-100" + name="{{ $key }}"> + + @foreach ($value['options'] as $option=>$display) + <option value="{{ $option }}" + {{ $value['value'] == $option ? 'selected' : '' }}> + {{ __($display) }} + </option> + @endforeach + </select> + @break + + @case($value['type'] == 'multiselect') + <select id="{{ $key }}" + class="custom-select w-100" + name="{{ $key }}[]" + multiple> + @foreach ($value['options'] as $option) + <option value="{{ $option }}" + {{ strpos($value['value'],$option) !== false ? 'selected' : '' }}> + {{ __($option) }} + </option> + @endforeach + </select> + @break + + @case($value['type'] == 'textarea') + <textarea class="form-control" + name="{{ $key }}" + rows="3">{{ $value['value'] }}</textarea> + @break + + @default + @endswitch + @error($key) <div class="text-danger "> {{ $message }} </div> - @enderror - </div> + @enderror + </div> - </div> + </div> + </div> </div> - </div> - @endforeach + @endforeach - <!-- TODO: Display this only on the General tab + <!-- TODO: Display this only on the General tab <div class="row"> <div class="col-4 d-flex align-items-center"> @@ -230,10 +281,10 @@ class="custom-select w-100" name="{{ $key }}[]" <div class="w-100"> <div class="input-group mb-3"> {!! htmlScriptTagJsApi() !!} - {!! htmlFormSnippet() !!} - @error('g-recaptcha-response') - <span class="text-danger" role="alert"> - <small><strong>{{ $message }}</strong></small> + {!! htmlFormSnippet() !!} + @error('g-recaptcha-response') + <span class="text-danger" role="alert"> + <small><strong>{{ $message }}</strong></small> </span> @enderror </div> @@ -243,16 +294,18 @@ class="custom-select w-100" name="{{ $key }}[]" --> - <div class="row"> - <div class="col-12 d-flex align-items-center justify-content-end"> - <button type="submit" - class="btn btn-primary float-right ">Save</button> - <button type="reset" - class="btn btn-secondary float-right ml-2">Reset</button> + <div class="row"> + <div class="col-12 d-flex align-items-center justify-content-end"> + <button type="submit" + class="btn btn-primary float-right ">Save + </button> + <button type="reset" + class="btn btn-secondary float-right ml-2">Reset + </button> + </div> </div> - </div> - </form> - </div> + </form> + </div> @endcanany @endforeach @@ -261,9 +314,8 @@ class="btn btn-secondary float-right ml-2">Reset</button> </div> </div> </div> - </div> </div> - + </div> <!-- END CUSTOM CONTENT --> @@ -277,7 +329,7 @@ class="btn btn-secondary float-right ml-2">Reset</button> $('.nav-item a[href="' + tabPaneHash + '"]').tab('show'); } - $('.nav-pills a').click(function(e) { + $('.nav-pills a').click(function (e) { $(this).tab('show'); const scrollmem = $('body').scrollTop(); window.location.hash = this.hash; From 1c179702d0aa473b254bd9d8adc4b68e9db42cbf Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 11 May 2023 09:32:13 +0200 Subject: [PATCH 169/514] Re-Implement Image uploading --- .../Controllers/Admin/SettingsController.php | 21 ++++++++ routes/web.php | 7 ++- .../views/admin/settings/index.blade.php | 54 +++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 7d1657f1d..47cc4207d 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -138,4 +138,25 @@ public function update(Request $request) return Redirect::to('admin/settings' . '#' . $category)->with('success', 'Settings updated successfully.'); } + + public function updateIcons(Request $request) + { + $request->validate([ + 'icon' => 'nullable|max:10000|mimes:jpg,png,jpeg', + 'logo' => 'nullable|max:10000|mimes:jpg,png,jpeg', + 'favicon' => 'nullable|max:10000|mimes:ico', + ]); + + if ($request->hasFile('icon')) { + $request->file('icon')->storeAs('public', 'icon.png'); + } + if ($request->hasFile('logo')) { + $request->file('logo')->storeAs('public', 'logo.png'); + } + if ($request->hasFile('favicon')) { + $request->file('favicon')->storeAs('public', 'favicon.ico'); + } + + return Redirect::to('admin/settings')->with('success', 'Icons updated successfully.'); + } } diff --git a/routes/web.php b/routes/web.php index 425c56135..5e09c048b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -169,13 +169,12 @@ Route::get('payments', [PaymentController::class, 'index'])->name('payments.index'); //settings - Route::get('settings/datatable', [SettingsController::class, 'datatable'])->name('settings.datatable'); - Route::patch('settings/updatevalue', [SettingsController::class, 'updatevalue'])->name('settings.updatevalue'); - Route::get('settings/checkPteroClientkey', [System::class, 'checkPteroClientkey'])->name('settings.checkPteroClientkey'); - Route::redirect('settings#system', 'system')->name('settings.system'); + Route::get('settings', [SettingsController::class, 'index'])->name('settings.index'); Route::post('settings', [SettingsController::class, 'update'])->name('settings.update'); + Route::post('settings/icons', [SettingsController::class, 'updateIcons'])->name('settings.updateIcons'); + //invoices Route::get('invoices/download-invoices', [InvoiceController::class, 'downloadAllInvoices'])->name('invoices.downloadAllInvoices'); diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 28615dbee..f3438dd2b 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -44,6 +44,17 @@ <nav class="mt-1"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" data-accordion="false"> + <li class="nav-item border-bottom-0"> + <a href="#icons" + class="nav-link" data-toggle="pill" + role="tab"> + <i + class="nav-icon fas fa-image"></i> + <p> + {{ __("Images / Icons") }} + </p> + </a> + </li> @foreach ($settings as $category => $options) @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) <li class="nav-item border-bottom-0"> @@ -66,6 +77,48 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> <!-- Content in $settings --> <div class="col-10 p-0"> <div class="tab-content ml-3" style="width: 100%;"> + <div container class="tab-pane fade container" + id="icons" role="tabpanel"> + + <form method="POST" enctype="multipart/form-data" class="mb-3" + action="{{ route('admin.settings.updateIcons') }}"> + @csrf + @method('POST') + <div class="row"> + <div class="card ml-5" style="width: 18rem;"> + <span class="h3 text-center">{{__("FavIcon")}} </span> + <div class="card-body"> + + </div> + <input type="file" accept="image/x-icon" class="form-control" name="favicon" + id="favicon"> + </div> + + <div class="card ml-5" style="width: 18rem;"> + <span class="h3 text-center">{{__("Icon")}} </span> + <img src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..." > + <div class="card-body"> + + </div> + <input type="file" accept="image/png,image/jpeg,image/jpg" class="form-control" + name="icon" id="icon"> + </div> + + <div class="card ml-5" style="width: 18rem;"> + <span class="h3 text-center">{{__("Login-page Logo")}} </span> + <img src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..." > + <div class="card-body"> + + </div> + <input type="file" accept="image/png,image/jpeg,image/jpg" class="form-control" + name="logo" id="logo"> + </div> + </div> + <div class="row"> + <button class="btn btn-primary ml-3 mt-3">{{ __('Save') }}</button> + </div> + </form> + </div> @foreach ($settings as $category => $options) @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) <div container class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" @@ -208,6 +261,7 @@ class="btn btn-secondary float-right ml-2">Reset</button> </div> </div> </div> + </div> </div> From ce65b9860ccc454680bb26168413ef115c7a1b7b Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 11 May 2023 11:06:30 +0200 Subject: [PATCH 170/514] Collapseable extension settings --- .../views/admin/settings/index.blade.php | 310 ++++++++++-------- 1 file changed, 181 insertions(+), 129 deletions(-) diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index f3438dd2b..3c396a8ce 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -12,7 +12,8 @@ <ol class="breadcrumb float-sm-right"> <li class="breadcrumb-item"><a href="">{{ __('Dashboard') }}</a></li> <li class="breadcrumb-item"><a class="text-muted" - href="{{ route('admin.settings.index') }}">{{ __('Settings') }}</a></li> + href="{{ route('admin.settings.index') }}">{{ __('Settings') }}</a> + </li> </ol> </div> </div> @@ -24,7 +25,9 @@ <h4>{{ __('The installer is not locked!') }}</h4> <p>{{ __('please create a file called "install.lock" in your dashboard Root directory. Otherwise no settings will be loaded!') }} </p> - <a href="/install?step=7"><button class="btn btn-outline-danger">{{ __('or click here') }}</button></a> + <a href="/install?step=7"> + <button class="btn btn-outline-danger">{{ __('or click here') }}</button> + </a> </div> @endif @@ -56,21 +59,54 @@ class="nav-icon fas fa-image"></i> </a> </li> @foreach ($settings as $category => $options) - @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) - <li class="nav-item border-bottom-0"> - <a href="#{{ $category }}" - class="nav-link {{ $loop->first ? 'active' : '' }}" data-toggle="pill" - role="tab"> - <i - class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> - <p> - {{ $category }} - </p> - </a> - </li> - @endcanany + @if(!str_contains($options['settings_class'],"Extension")) + @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) + <li class="nav-item border-bottom-0"> + <a href="#{{ $category }}" + class="nav-link {{ $loop->first ? 'active' : '' }}" + data-toggle="pill" + role="tab"> + <i + class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> + <p> + {{ $category }} + </p> + </a> + </li> + @endcanany + @endif @endforeach </ul> + <i class="fa-solid fa-up-right-from-square"></i> + <button class="btn btn-outline-secondary" type="button" data-toggle="collapse" + data-target="#collapseExtensions" aria-expanded="false" + aria-controls="collapseExample"> + {{__("Extension Settings")}} + </button> + </p> + <div class="collapse" id="collapseExtensions"> + <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" + data-accordion="false"> + @foreach ($settings as $category => $options) + @if(str_contains($options['settings_class'],"Extension")) + + @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) + <li class="nav-item border-bottom-0"> + <a href="#{{ $category }}" + class="nav-link" + data-toggle="pill" + role="tab"> + <i class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> + <p> + {{ $category }} + </p> + </a> + </li> + @endcanany + @endif + @endforeach + </div> + </ul> </nav> </div> <!-- /.sidebar-menu --> @@ -85,32 +121,41 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> @csrf @method('POST') <div class="row"> - <div class="card ml-5" style="width: 18rem;"> - <span class="h3 text-center">{{__("FavIcon")}} </span> - <div class="card-body"> + <div class="card ml-5" style="width: 18rem;"> + <span class="h3 text-center">{{__("FavIcon")}} </span> + <div class="card-body"> - </div> - <input type="file" accept="image/x-icon" class="form-control" name="favicon" - id="favicon"> </div> + <input type="file" accept="image/x-icon" class="form-control" + name="favicon" + id="favicon"> + </div> <div class="card ml-5" style="width: 18rem;"> <span class="h3 text-center">{{__("Icon")}} </span> - <img src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..." > + <img + src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" + style="width:5vw;display: block; margin-left: auto;margin-right: auto" + class="card-img-top" alt="..."> <div class="card-body"> </div> - <input type="file" accept="image/png,image/jpeg,image/jpg" class="form-control" + <input type="file" accept="image/png,image/jpeg,image/jpg" + class="form-control" name="icon" id="icon"> </div> <div class="card ml-5" style="width: 18rem;"> <span class="h3 text-center">{{__("Login-page Logo")}} </span> - <img src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..." > + <img + src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" + style="width:5vw;display: block; margin-left: auto;margin-right: auto" + class="card-img-top" alt="..."> <div class="card-body"> </div> - <input type="file" accept="image/png,image/jpeg,image/jpg" class="form-control" + <input type="file" accept="image/png,image/jpeg,image/jpg" + class="form-control" name="logo" id="logo"> </div> </div> @@ -121,104 +166,110 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> </div> @foreach ($settings as $category => $options) @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) - <div container class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" - id="{{ $category }}" role="tabpanel"> - - <form action="{{ route('admin.settings.update') }}" method="POST"> - @csrf - @method('POST') - <input type="hidden" name="settings_class" - value="{{ $options['settings_class'] }}"> - <input type="hidden" name="category" value="{{ $category }}"> - - @foreach ($options as $key => $value) - @if ($key == 'category_icon' || $key == 'settings_class') - @continue - @endif - <div class="row"> - <div class="col-4 d-flex align-items-center"> - <label for="{{ $key }}">{{ $value['label'] }}</label> - </div> + <div + class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" + id="{{ $category }}" role="tabpanel"> + + <form action="{{ route('admin.settings.update') }}" method="POST"> + @csrf + @method('POST') + <input type="hidden" name="settings_class" + value="{{ $options['settings_class'] }}"> + <input type="hidden" name="category" value="{{ $category }}"> + + @foreach ($options as $key => $value) + @if ($key == 'category_icon' || $key == 'settings_class') + @continue + @endif + <div class="row"> + <div class="col-4 d-flex align-items-center"> + <label for="{{ $key }}">{{ $value['label'] }}</label> + </div> - <div class="col-8"> - <div class="custom-control mb-3 d-flex align-items-center"> - @if ($value['description']) - <i class="fas fa-info-circle mr-4" data-toggle="popover" - data-trigger="hover" data-placement="top" - data-html="true" - data-content="{{ $value['description'] }}"></i> - @else - <i class="fas fa-info-circle mr-4 invisible"></i> - @endif - - <div class="w-100"> - @switch($value) - @case($value['type'] == 'string') - <input type="text" class="form-control" - name="{{ $key }}" - value="{{ $value['value'] }}"> - @break - - @case($value['type'] == 'boolean') - <input type="checkbox" name="{{ $key }}" - value="{{ $value['value'] }}" - {{ $value['value'] ? 'checked' : '' }}> - @break - - @case($value['type'] == 'number') - <input type="number" class="form-control" - name="{{ $key }}" - value="{{ $value['value'] }}"> - @break - - @case($value['type'] == 'select') - <select id="{{ $key }}" - class="custom-select w-100" name="{{ $key }}"> - - @foreach ($value['options'] as $option=>$display) - <option value="{{ $option }}" - {{ $value['value'] == $option ? 'selected' : '' }}> - {{ __($display) }} - </option> - @endforeach - </select> - @break - - @case($value['type'] == 'multiselect') - <select id="{{ $key }}" - class="custom-select w-100" name="{{ $key }}[]" - multiple> - @foreach ($value['options'] as $option) - <option value="{{ $option }}" - {{ strpos($value['value'],$option) !== false ? 'selected' : '' }}> - {{ __($option) }} - </option> - @endforeach - </select> - @break - - @case($value['type'] == 'textarea') - <textarea class="form-control" name="{{ $key }}" rows="3">{{ $value['value'] }}</textarea> - @break - - @default - @endswitch - @error($key) + <div class="col-8"> + <div class="custom-control mb-3 d-flex align-items-center"> + @if ($value['description']) + <i class="fas fa-info-circle mr-4" + data-toggle="popover" + data-trigger="hover" data-placement="top" + data-html="true" + data-content="{{ $value['description'] }}"></i> + @else + <i class="fas fa-info-circle mr-4 invisible"></i> + @endif + + <div class="w-100"> + @switch($value) + @case($value['type'] == 'string') + <input type="text" class="form-control" + name="{{ $key }}" + value="{{ $value['value'] }}"> + @break + + @case($value['type'] == 'boolean') + <input type="checkbox" name="{{ $key }}" + value="{{ $value['value'] }}" + {{ $value['value'] ? 'checked' : '' }}> + @break + + @case($value['type'] == 'number') + <input type="number" class="form-control" + name="{{ $key }}" + value="{{ $value['value'] }}"> + @break + + @case($value['type'] == 'select') + <select id="{{ $key }}" + class="custom-select w-100" + name="{{ $key }}"> + + @foreach ($value['options'] as $option=>$display) + <option value="{{ $option }}" + {{ $value['value'] == $option ? 'selected' : '' }}> + {{ __($display) }} + </option> + @endforeach + </select> + @break + + @case($value['type'] == 'multiselect') + <select id="{{ $key }}" + class="custom-select w-100" + name="{{ $key }}[]" + multiple> + @foreach ($value['options'] as $option) + <option value="{{ $option }}" + {{ strpos($value['value'],$option) !== false ? 'selected' : '' }}> + {{ __($option) }} + </option> + @endforeach + </select> + @break + + @case($value['type'] == 'textarea') + <textarea class="form-control" + name="{{ $key }}" + rows="3">{{ $value['value'] }}</textarea> + @break + + @default + @endswitch + @error($key) <div class="text-danger "> {{ $message }} </div> - @enderror - </div> + @enderror + </div> - </div> + </div> + </div> </div> - </div> - @endforeach + @endforeach - <!-- TODO: Display this only on the General tab + <!-- TODO: Display this only on the General tab <div class="row"> <div class="col-4 d-flex align-items-center"> @@ -230,10 +281,10 @@ class="custom-select w-100" name="{{ $key }}[]" <div class="w-100"> <div class="input-group mb-3"> {!! htmlScriptTagJsApi() !!} - {!! htmlFormSnippet() !!} - @error('g-recaptcha-response') - <span class="text-danger" role="alert"> - <small><strong>{{ $message }}</strong></small> + {!! htmlFormSnippet() !!} + @error('g-recaptcha-response') + <span class="text-danger" role="alert"> + <small><strong>{{ $message }}</strong></small> </span> @enderror </div> @@ -243,16 +294,18 @@ class="custom-select w-100" name="{{ $key }}[]" --> - <div class="row"> - <div class="col-12 d-flex align-items-center justify-content-end"> - <button type="submit" - class="btn btn-primary float-right ">Save</button> - <button type="reset" - class="btn btn-secondary float-right ml-2">Reset</button> + <div class="row"> + <div class="col-12 d-flex align-items-center justify-content-end"> + <button type="submit" + class="btn btn-primary float-right ">Save + </button> + <button type="reset" + class="btn btn-secondary float-right ml-2">Reset + </button> + </div> </div> - </div> - </form> - </div> + </form> + </div> @endcanany @endforeach @@ -261,9 +314,8 @@ class="btn btn-secondary float-right ml-2">Reset</button> </div> </div> </div> - </div> </div> - + </div> <!-- END CUSTOM CONTENT --> @@ -277,7 +329,7 @@ class="btn btn-secondary float-right ml-2">Reset</button> $('.nav-item a[href="' + tabPaneHash + '"]').tab('show'); } - $('.nav-pills a').click(function(e) { + $('.nav-pills a').click(function (e) { $(this).tab('show'); const scrollmem = $('body').scrollTop(); window.location.hash = this.hash; From b88727940cf3a4648eddea3eb1801e9887bd5282 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 11 May 2023 11:11:22 +0200 Subject: [PATCH 171/514] clean up --- themes/default/views/admin/settings/index.blade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 3c396a8ce..7c9f59b5b 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -77,13 +77,13 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> @endif @endforeach </ul> - <i class="fa-solid fa-up-right-from-square"></i> + <button class="btn btn-outline-secondary" type="button" data-toggle="collapse" data-target="#collapseExtensions" aria-expanded="false" - aria-controls="collapseExample"> + aria-controls="collapseExtensions"> {{__("Extension Settings")}} </button> - </p> + <div class="collapse" id="collapseExtensions"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" data-accordion="false"> From 21529ddf65326a0de12bb371365e347e93164a1b Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 12 May 2023 15:52:58 +0200 Subject: [PATCH 172/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Number=20Input=20?= =?UTF-8?q?steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/admin/products/create.blade.php | 355 +++++++++--------- .../views/admin/products/edit.blade.php | 351 +++++++++-------- 2 files changed, 347 insertions(+), 359 deletions(-) diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index 082c24541..c6c5427f6 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -6,15 +6,15 @@ <div class="container-fluid"> <div class="row mb-2"> <div class="col-sm-6"> - <h1>{{__('Products')}}</h1> + <h1>{{ __('Products') }}</h1> </div> <div class="col-sm-6"> <ol class="breadcrumb float-sm-right"> - <li class="breadcrumb-item"><a href="{{ route('home') }}">{{__('Dashboard')}}</a></li> - <li class="breadcrumb-item"><a href="{{ route('admin.products.index') }}">{{__('Products')}}</a> + <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li> + <li class="breadcrumb-item"><a href="{{ route('admin.products.index') }}">{{ __('Products') }}</a> </li> <li class="breadcrumb-item"><a class="text-muted" - href="{{ route('admin.products.create') }}">{{__('Create')}}</a> + href="{{ route('admin.products.create') }}">{{ __('Create') }}</a> </li> </ol> </div> @@ -26,23 +26,23 @@ <!-- MAIN CONTENT --> <section class="content"> <div class="container-fluid"> - <form action="{{route('admin.products.store')}}" method="POST"> + <form action="{{ route('admin.products.store') }}" method="POST"> @csrf <div class="row"> <div class="col-lg-6"> <div class="card"> <div class="card-header"> - <h5 class="card-title">{{__('Product Details')}}</h5> + <h5 class="card-title">{{ __('Product Details') }}</h5> </div> <div class="card-body"> <div class="d-flex flex-row-reverse"> <div class="custom-control custom-switch"> <input type="checkbox" name="disabled" - class="custom-control-input custom-control-input-danger" id="switch1"> - <label class="custom-control-label" for="switch1">{{__('Disabled')}} <i + class="custom-control-input custom-control-input-danger" id="switch1"> + <label class="custom-control-label" for="switch1">{{ __('Disabled') }} <i data-toggle="popover" data-trigger="hover" - data-content="{{__('Will hide this option from being selected')}}" + data-content="{{ __('Will hide this option from being selected') }}" class="fas fa-info-circle"></i></label> </div> </div> @@ -50,219 +50,209 @@ class="fas fa-info-circle"></i></label> <div class="row"> <div class="col-lg-6"> <div class="form-group"> - <label for="name">{{__('Name')}}</label> - <input value="{{$product->name ?? old('name')}}" id="name" name="name" - type="text" - class="form-control @error('name') is-invalid @enderror" - required="required"> + <label for="name">{{ __('Name') }}</label> + <input value="{{ $product->name ?? old('name') }}" id="name" + name="name" type="text" + class="form-control @error('name') is-invalid @enderror" + required="required"> @error('name') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="price">{{__('Price in')}} {{ $credits_display_name }}</label> - <input value="{{$product->price ?? old('price')}}" id="price" name="price" step=".01" - type="number" - step="0.0001" - class="form-control @error('price') is-invalid @enderror" - required="required"> + <label for="price">{{ __('Price in') }} {{ $credits_display_name }}</label> + <input value="{{ $product->price ?? old('price') }}" id="price" + name="price" step=".0001" type="number" + class="form-control @error('price') is-invalid @enderror" + required="required"> @error('price') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="memory">{{__('Memory')}}</label> - <input value="{{$product->memory ?? old('memory')}}" id="memory" - name="memory" - type="number" - class="form-control @error('memory') is-invalid @enderror" - required="required"> + <label for="memory">{{ __('Memory') }}</label> + <input value="{{ $product->memory ?? old('memory') }}" id="memory" + name="memory" type="number" + class="form-control @error('memory') is-invalid @enderror" + required="required"> @error('memory') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="cpu">{{__('Cpu')}}</label> - <input value="{{$product->cpu ?? old('cpu')}}" id="cpu" name="cpu" - type="number" - class="form-control @error('cpu') is-invalid @enderror" - required="required"> + <label for="cpu">{{ __('Cpu') }}</label> + <input value="{{ $product->cpu ?? old('cpu') }}" id="cpu" name="cpu" + type="number" class="form-control @error('cpu') is-invalid @enderror" + required="required"> @error('cpu') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="swap">{{__('Swap')}}</label> - <input value="{{$product->swap ?? old('swap')}}" id="swap" name="swap" - type="number" - class="form-control @error('swap') is-invalid @enderror" - required="required"> + <label for="swap">{{ __('Swap') }}</label> + <input value="{{ $product->swap ?? old('swap') }}" id="swap" + name="swap" type="number" + class="form-control @error('swap') is-invalid @enderror" + required="required"> @error('swap') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="allocations">{{__('Allocations')}}</label> - <input value="{{$product->allocations ?? old('allocations') ?? 0}}" - id="allocations" name="allocations" - type="number" - class="form-control @error('allocations') is-invalid @enderror" - required="required"> + <label for="allocations">{{ __('Allocations') }}</label> + <input value="{{ $product->allocations ?? (old('allocations') ?? 0) }}" + id="allocations" name="allocations" type="number" + class="form-control @error('allocations') is-invalid @enderror" + required="required"> @error('allocations') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="description">{{__('Description')}} <i data-toggle="popover" - data-trigger="hover" - data-content="{{__('This is what the users sees')}}" - class="fas fa-info-circle"></i></label> - <textarea id="description" name="description" - type="text" - class="form-control @error('description') is-invalid @enderror" - required="required">{{$product->description ?? old('description')}}</textarea> + <label for="description">{{ __('Description') }} <i data-toggle="popover" + data-trigger="hover" + data-content="{{ __('This is what the users sees') }}" + class="fas fa-info-circle"></i></label> + <textarea id="description" name="description" type="text" + class="form-control @error('description') is-invalid @enderror" required="required">{{ $product->description ?? old('description') }}</textarea> @error('description') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <input type="checkbox" value="1" id="oom" name="oom_killer" - class=""> + <input type="checkbox" value="1" id="oom" name="oom_killer" + class=""> - <label for="oom_killer">{{__('OOM Killer')}} <i - data-toggle="popover" data-trigger="hover" - data-content="{{__('Enable or Disable the OOM Killer for this Product.')}}" + <label for="oom_killer">{{ __('OOM Killer') }} <i data-toggle="popover" + data-trigger="hover" + data-content="{{ __('Enable or Disable the OOM Killer for this Product.') }}" class="fas fa-info-circle"></i></label> </div> </div> <div class="col-lg-6"> <div class="form-group"> - <label for="disk">{{__('Disk')}}</label> - <input value="{{$product->disk ?? old('disk') ?? 1000}}" id="disk" - name="disk" - type="number" - class="form-control @error('disk') is-invalid @enderror" - required="required"> + <label for="disk">{{ __('Disk') }}</label> + <input value="{{ $product->disk ?? (old('disk') ?? 1000) }}" id="disk" + name="disk" type="number" + class="form-control @error('disk') is-invalid @enderror" + required="required"> @error('disk') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="billing_period">{{__('Billing Period')}} <i + <label for="billing_period">{{ __('Billing Period') }} <i data-toggle="popover" data-trigger="hover" - data-content="{{__('Period when the user will be charged for the given price')}}" + data-content="{{ __('Period when the user will be charged for the given price') }}" class="fas fa-info-circle"></i></label> - <select id="billing_period" style="width:100%" class="custom-select" name="billing_period" required - autocomplete="off" @error('billing_period') is-invalid @enderror> - <option value="hourly" selected> - {{__('Hourly')}} - </option> - <option value="daily"> - {{__('Daily')}} - </option> - <option value="weekly"> - {{__('Weekly')}} - </option> - <option value="monthly"> - {{__('Monthly')}} - </option> - <option value="quarterly"> - {{__('Quarterly')}} - </option> - <option value="half-annually"> - {{__('Half Annually')}} - </option> - <option value="annually"> - {{__('Annually')}} - </option> + <select id="billing_period" style="width:100%" class="custom-select" + name="billing_period" required autocomplete="off" + @error('billing_period') is-invalid @enderror> + <option value="hourly" selected> + {{ __('Hourly') }} + </option> + <option value="daily"> + {{ __('Daily') }} + </option> + <option value="weekly"> + {{ __('Weekly') }} + </option> + <option value="monthly"> + {{ __('Monthly') }} + </option> + <option value="quarterly"> + {{ __('Quarterly') }} + </option> + <option value="half-annually"> + {{ __('Half Annually') }} + </option> + <option value="annually"> + {{ __('Annually') }} + </option> </select> @error('billing_period') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="minimum_credits">{{__('Minimum')}} {{ $credits_display_name }} <i - data-toggle="popover" data-trigger="hover" - data-content="{{__('Setting to -1 will use the value from configuration.')}}" + <label for="minimum_credits">{{ __('Minimum') }} {{ $credits_display_name }} + <i data-toggle="popover" data-trigger="hover" + data-content="{{ __('Setting to -1 will use the value from configuration.') }}" class="fas fa-info-circle"></i></label> <input - value="{{ $product->minimum_credits ?? old('minimum_credits') ?? -1 }}" - id="minimum_credits" - name="minimum_credits" type="number" + value="{{ $product->minimum_credits ?? (old('minimum_credits') ?? -1) }}" + id="minimum_credits" name="minimum_credits" type="number" class="form-control @error('minimum_credits') is-invalid @enderror" required="required"> @error('minimum_credits') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="io">{{__('IO')}}</label> - <input value="{{$product->io ?? old('io') ?? 500}}" id="io" name="io" - type="number" - class="form-control @error('io') is-invalid @enderror" - required="required"> + <label for="io">{{ __('IO') }}</label> + <input value="{{ $product->io ?? (old('io') ?? 500) }}" id="io" + name="io" type="number" + class="form-control @error('io') is-invalid @enderror" + required="required"> @error('io') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="databases">{{__('Databases')}}</label> - <input value="{{$product->databases ?? old('databases') ?? 1}}" - id="databases" - name="databases" - type="number" - class="form-control @error('databases') is-invalid @enderror" - required="required"> + <label for="databases">{{ __('Databases') }}</label> + <input value="{{ $product->databases ?? (old('databases') ?? 1) }}" + id="databases" name="databases" type="number" + class="form-control @error('databases') is-invalid @enderror" + required="required"> @error('databases') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="backups">{{__('Backups')}}</label> - <input value="{{$product->backups ?? old('backups') ?? 1}}" id="backups" - name="backups" - type="number" - class="form-control @error('backups') is-invalid @enderror" - required="required"> + <label for="backups">{{ __('Backups') }}</label> + <input value="{{ $product->backups ?? (old('backups') ?? 1) }}" + id="backups" name="backups" type="number" + class="form-control @error('backups') is-invalid @enderror" + required="required"> @error('backups') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> </div> @@ -270,7 +260,7 @@ class="form-control @error('backups') is-invalid @enderror" <div class="form-group text-right"> <button type="submit" class="btn btn-primary"> - {{__('Submit')}} + {{ __('Submit') }} </button> </div> @@ -281,66 +271,69 @@ class="form-control @error('backups') is-invalid @enderror" <div class="col-lg-6"> <div class="card"> <div class="card-header"> - <h5 class="card-title">{{__('Product Linking')}} - <i data-toggle="popover" - data-trigger="hover" - data-content="{{__('Link your products to nodes and eggs to create dynamic pricing for each option')}}" - class="fas fa-info-circle"></i></h5> + <h5 class="card-title">{{ __('Product Linking') }} + <i data-toggle="popover" data-trigger="hover" + data-content="{{ __('Link your products to nodes and eggs to create dynamic pricing for each option') }}" + class="fas fa-info-circle"></i> + </h5> </div> <div class="card-body"> <div class="form-group"> - <label for="nodes">{{__('Nodes')}}</label> + <label for="nodes">{{ __('Nodes') }}</label> <select id="nodes" style="width:100%" - class="custom-select @error('nodes') is-invalid @enderror" - name="nodes[]" multiple="multiple" autocomplete="off"> - @foreach($locations as $location) - <optgroup label="{{$location->name}}"> - @foreach($location->nodes as $node) + class="custom-select @error('nodes') is-invalid @enderror" name="nodes[]" + multiple="multiple" autocomplete="off"> + @foreach ($locations as $location) + <optgroup label="{{ $location->name }}"> + @foreach ($location->nodes as $node) <option - @if(isset($product)) @if($product->nodes->contains('id' , $node->id)) selected - @endif @endif value="{{$node->id}}">{{$node->name}}</option> + @if (isset($product)) @if ($product->nodes->contains('id', $node->id)) selected @endif + @endif + value="{{ $node->id }}">{{ $node->name }}</option> @endforeach </optgroup> @endforeach </select> @error('nodes') - <div class="text-danger"> - {{$message}} - </div> + <div class="text-danger"> + {{ $message }} + </div> @enderror <div class="text-muted"> - {{__('This product will only be available for these nodes')}} + {{ __('This product will only be available for these nodes') }} </div> </div> <div class="form-group"> - <label for="eggs">{{__('Eggs')}}</label> + <label for="eggs">{{ __('Eggs') }}</label> <select id="eggs" style="width:100%" - class="custom-select @error('eggs') is-invalid @enderror" - name="eggs[]" multiple="multiple" autocomplete="off"> - @foreach($nests as $nest) - <optgroup label="{{$nest->name}}"> - @foreach($nest->eggs as $egg) + class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" + multiple="multiple" autocomplete="off"> + @foreach ($nests as $nest) + <optgroup label="{{ $nest->name }}"> + @foreach ($nest->eggs as $egg) <option - @if(isset($product)) @if($product->eggs->contains('id' , $egg->id)) selected - @endif @endif value="{{$egg->id}}">{{$egg->name}}</option> + @if (isset($product)) @if ($product->eggs->contains('id', $egg->id)) selected @endif + @endif + value="{{ $egg->id }}">{{ $egg->name }}</option> @endforeach </optgroup> @endforeach </select> @error('eggs') - <div class="text-danger"> - {{$message}} - </div> + <div class="text-danger"> + {{ $message }} + </div> @enderror <div class="text-muted"> - {{__('This product will only be available for these eggs')}} + {{ __('This product will only be available for these eggs') }} </div> </div> <div class="text-muted"> - {{__('No Eggs or Nodes shown?')}} <a href="{{route('admin.overview.sync')}}">{{__("Sync now")}}</a> + {{ __('No Eggs or Nodes shown?') }} <a + href="{{ route('admin.overview.sync') }}">{{ __('Sync now') }}</a> </div> </div> </div> @@ -354,7 +347,7 @@ class="custom-select @error('eggs') is-invalid @enderror" <!-- END CONTENT --> <script> - document.addEventListener('DOMContentLoaded', function () { + document.addEventListener('DOMContentLoaded', function() { $('[data-toggle="popover"]').popover(); $('.custom-select').select2(); }); diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index 7487df2e1..8f7649580 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -6,15 +6,15 @@ <div class="container-fluid"> <div class="row mb-2"> <div class="col-sm-6"> - <h1>{{__('Products')}}</h1> + <h1>{{ __('Products') }}</h1> </div> <div class="col-sm-6"> <ol class="breadcrumb float-sm-right"> - <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> - <li class="breadcrumb-item"><a href="{{route('admin.products.index')}}">{{__('Products')}}</a> + <li class="breadcrumb-item"><a href="{{ route('home') }}">{{ __('Dashboard') }}</a></li> + <li class="breadcrumb-item"><a href="{{ route('admin.products.index') }}">{{ __('Products') }}</a> </li> <li class="breadcrumb-item"><a class="text-muted" - href="{{route('admin.products.edit' , $product->id)}}">{{__('Edit')}}</a> + href="{{ route('admin.products.edit', $product->id) }}">{{ __('Edit') }}</a> </li> </ol> </div> @@ -26,35 +26,39 @@ <!-- MAIN CONTENT --> <section class="content"> <div class="container-fluid"> - <form action="{{route('admin.products.update' , $product->id)}}" method="POST"> + <form action="{{ route('admin.products.update', $product->id) }}" method="POST"> @csrf @method('PATCH') <div class="row"> <div class="col-lg-6"> - @if($product->servers()->count() > 0) + @if ($product->servers()->count() > 0) <div class="callout callout-danger"> - <h4>{{__('Editing the resource options will not automatically update the servers on - pterodactyls side!')}}'</h4> - <p class="text-muted">{{__('Automatically updating resource options on pterodactyl side is on - my todo list :)')}}</p> + <h4>{{ __('Editing the resource options will not automatically update the servers on + pterodactyls side!') }}' + </h4> + <p class="text-muted"> + {{ __('Automatically updating resource options on pterodactyl side is on + my todo list :)') }} + </p> </div> @endif <div class="card"> <div class="card-header"> - <h5 class="card-title">{{__('Product Details')}}</h5> + <h5 class="card-title">{{ __('Product Details') }}</h5> </div> <div class="card-body"> <div class="d-flex flex-row-reverse"> <div class="custom-control custom-switch"> - <input type="checkbox" @if($product->disabled) checked @endif name="disabled" - class="custom-control-input custom-control-input-danger" id="switch1"> - <label class="custom-control-label" for="switch1">{{__('Disabled')}} <i + <input type="checkbox" @if ($product->disabled) checked @endif + name="disabled" class="custom-control-input custom-control-input-danger" + id="switch1"> + <label class="custom-control-label" for="switch1">{{ __('Disabled') }} <i data-toggle="popover" data-trigger="hover" - data-content="{{__('Will hide this option from being selected')}}" + data-content="{{ __('Will hide this option from being selected') }}" class="fas fa-info-circle"></i></label> </div> </div> @@ -63,211 +67,203 @@ class="fas fa-info-circle"></i></label> <div class="col-lg-6"> <div class="form-group"> - <label for="name">{{__('Name')}}</label> - <input value="{{ $product->name }}" id="name" name="name" type="text" - class="form-control @error('name') is-invalid @enderror" - required="required"> + <label for="name">{{ __('Name') }}</label> + <input value="{{ $product->name }}" id="name" name="name" + type="text" class="form-control @error('name') is-invalid @enderror" + required="required"> @error('name') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="price">{{__('Price in')}} {{ $credits_display_name }}</label> - <input value="{{$product->price}}" id="price" name="price" - type="number" - step="0.0001" - class="form-control @error('price') is-invalid @enderror" - required="required"> + <label for="price">{{ __('Price in') }} {{ $credits_display_name }}</label> + <input value="{{ $product->price }}" id="price" name="price" + type="number" step=".0001" + class="form-control @error('price') is-invalid @enderror" + required="required"> @error('price') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="memory">{{__('Memory')}}</label> + <label for="memory">{{ __('Memory') }}</label> <input value="{{ $product->memory }}" id="memory" name="memory" - type="number" - class="form-control @error('memory') is-invalid @enderror" - required="required"> + type="number" class="form-control @error('memory') is-invalid @enderror" + required="required"> @error('memory') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="cpu">{{__('Cpu')}}</label> + <label for="cpu">{{ __('Cpu') }}</label> <input value="{{ $product->cpu }}" id="cpu" name="cpu" type="number" - class="form-control @error('cpu') is-invalid @enderror" - required="required"> + class="form-control @error('cpu') is-invalid @enderror" required="required"> @error('cpu') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="swap">{{__('Swap')}}</label> - <input value="{{ $product->swap }}" id="swap" name="swap" type="number" - class="form-control @error('swap') is-invalid @enderror" - required="required"> + <label for="swap">{{ __('Swap') }}</label> + <input value="{{ $product->swap }}" id="swap" name="swap" + type="number" class="form-control @error('swap') is-invalid @enderror" + required="required"> @error('swap') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="allocations">{{__('Allocations')}}</label> - <input value="{{ $product->allocations }}" id="allocations" - name="allocations" type="number" - class="form-control @error('allocations') is-invalid @enderror" - required="required"> + <label for="allocations">{{ __('Allocations') }}</label> + <input value="{{ $product->allocations }}" id="allocations" name="allocations" + type="number" + class="form-control @error('allocations') is-invalid @enderror" + required="required"> @error('allocations') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="description">{{__('Description')}} <i data-toggle="popover" - data-trigger="hover" - data-content="{{__('This is what the users sees')}}" - class="fas fa-info-circle"></i></label> - <textarea id="description" name="description" - type="text" - class="form-control @error('description') is-invalid @enderror" - required="required">{{$product->description}}</textarea> + <label for="description">{{ __('Description') }} <i data-toggle="popover" + data-trigger="hover" + data-content="{{ __('This is what the users sees') }}" + class="fas fa-info-circle"></i></label> + <textarea id="description" name="description" type="text" + class="form-control @error('description') is-invalid @enderror" required="required">{{ $product->description }}</textarea> @error('description') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <input type="checkbox" @if($product->oom_killer) checked @endif value="1" id="oom" name="oom_killer" - class=""> + <input type="checkbox" @if ($product->oom_killer) checked @endif + value="1" id="oom" name="oom_killer" class=""> - <label for="oom_killer">{{__('OOM Killer')}} <i - data-toggle="popover" data-trigger="hover" - data-content="{{__('Enable or Disable the OOM Killer for this Product.')}}" + <label for="oom_killer">{{ __('OOM Killer') }} <i data-toggle="popover" + data-trigger="hover" + data-content="{{ __('Enable or Disable the OOM Killer for this Product.') }}" class="fas fa-info-circle"></i></label> </div> </div> <div class="col-lg-6"> <div class="form-group"> - <label for="disk">{{__('Disk')}}</label> - <input value="{{ $product->disk }}" id="disk" name="disk" type="number" - class="form-control @error('disk') is-invalid @enderror" - required="required"> + <label for="disk">{{ __('Disk') }}</label> + <input value="{{ $product->disk }}" id="disk" name="disk" + type="number" class="form-control @error('disk') is-invalid @enderror" + required="required"> @error('disk') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="billing_period">{{__('Billing Period')}} <i + <label for="billing_period">{{ __('Billing Period') }} <i data-toggle="popover" data-trigger="hover" - data-content="{{__('Period when the user will be charged for the given price')}}" + data-content="{{ __('Period when the user will be charged for the given price') }}" class="fas fa-info-circle"></i></label> - <select id="billing_period" style="width:100%" class="custom-select" name="billing_period" required - autocomplete="off" @error('billing_period') is-invalid @enderror> - <option value="hourly" @if ($product->billing_period == 'hourly') selected - @endif> - {{__('Hourly')}} - </option> - <option value="daily" @if ($product->billing_period == 'daily') selected - @endif> - {{__('Daily')}} - </option> - <option value="weekly" @if ($product->billing_period == 'weekly') selected - @endif> - {{__('Weekly')}} - </option> - <option value="monthly" @if ($product->billing_period == 'monthly') selected - @endif> - {{__('Monthly')}} - </option> - <option value="quarterly" @if ($product->billing_period == 'quarterly') selected - @endif> - {{__('Quarterly')}} - </option> - <option value="half-annually" @if ($product->billing_period == 'half-annually') selected - @endif> - {{__('Half Annually')}} - </option> - <option value="annually" @if ($product->billing_period == 'annually') selected - @endif> - {{__('Annually')}} - </option> + <select id="billing_period" style="width:100%" class="custom-select" + name="billing_period" required autocomplete="off" + @error('billing_period') is-invalid @enderror> + <option value="hourly" @if ($product->billing_period == 'hourly') selected @endif> + {{ __('Hourly') }} + </option> + <option value="daily" @if ($product->billing_period == 'daily') selected @endif> + {{ __('Daily') }} + </option> + <option value="weekly" @if ($product->billing_period == 'weekly') selected @endif> + {{ __('Weekly') }} + </option> + <option value="monthly" @if ($product->billing_period == 'monthly') selected @endif> + {{ __('Monthly') }} + </option> + <option value="quarterly" + @if ($product->billing_period == 'quarterly') selected @endif> + {{ __('Quarterly') }} + </option> + <option value="half-annually" + @if ($product->billing_period == 'half-annually') selected @endif> + {{ __('Half Annually') }} + </option> + <option value="annually" + @if ($product->billing_period == 'annually') selected @endif> + {{ __('Annually') }} + </option> </select> @error('billing_period') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="minimum_credits">{{__('Minimum')}} {{ $credits_display_name }} <i - data-toggle="popover" data-trigger="hover" - data-content="{{__('Setting to -1 will use the value from configuration.')}}" + <label for="minimum_credits">{{ __('Minimum') }} {{ $credits_display_name }} + <i data-toggle="popover" data-trigger="hover" + data-content="{{ __('Setting to -1 will use the value from configuration.') }}" class="fas fa-info-circle"></i></label> <input value="{{ $product->minimum_credits }}" id="minimum_credits" - name="minimum_credits" type="number" - class="form-control @error('minimum_credits') is-invalid @enderror" - required="required"> + name="minimum_credits" type="number" + class="form-control @error('minimum_credits') is-invalid @enderror" + required="required"> @error('minimum_credits') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="io">{{__('IO')}}</label> - <input value="{{ $product->io }}" id="io" name="io" type="number" - class="form-control @error('io') is-invalid @enderror" - required="required"> + <label for="io">{{ __('IO') }}</label> + <input value="{{ $product->io }}" id="io" name="io" + type="number" class="form-control @error('io') is-invalid @enderror" + required="required"> @error('io') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="databases">{{__('Databases')}}</label> + <label for="databases">{{ __('Databases') }}</label> <input value="{{ $product->databases }}" id="databases" name="databases" - type="number" - class="form-control @error('databases') is-invalid @enderror" - required="required"> + type="number" + class="form-control @error('databases') is-invalid @enderror" + required="required"> @error('databases') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> <div class="form-group"> - <label for="backups">{{__('Backups')}}</label> + <label for="backups">{{ __('Backups') }}</label> <input value="{{ $product->backups }}" id="backups" name="backups" - type="number" - class="form-control @error('backups') is-invalid @enderror" - required="required"> + type="number" + class="form-control @error('backups') is-invalid @enderror" + required="required"> @error('backups') - <div class="invalid-feedback"> - {{ $message }} - </div> + <div class="invalid-feedback"> + {{ $message }} + </div> @enderror </div> </div> @@ -275,7 +271,7 @@ class="form-control @error('backups') is-invalid @enderror" <div class="form-group text-right"> <button type="submit" class="btn btn-primary"> - {{__('Submit')}} + {{ __('Submit') }} </button> </div> @@ -286,59 +282,59 @@ class="form-control @error('backups') is-invalid @enderror" <div class="col-lg-6"> <div class="card"> <div class="card-header"> - <h5 class="card-title">{{__('Product Linking')}} - <i data-toggle="popover" - data-trigger="hover" - data-content="{{__('Link your products to nodes and eggs to create dynamic pricing for each option')}}" - class="fas fa-info-circle"></i></h5> + <h5 class="card-title">{{ __('Product Linking') }} + <i data-toggle="popover" data-trigger="hover" + data-content="{{ __('Link your products to nodes and eggs to create dynamic pricing for each option') }}" + class="fas fa-info-circle"></i> + </h5> </div> <div class="card-body"> <div class="form-group"> - <label for="nodes">{{__('Nodes')}}</label> + <label for="nodes">{{ __('Nodes') }}</label> <select id="nodes" style="width:100%" - class="custom-select @error('nodes') is-invalid @enderror" name="nodes[]" - multiple="multiple" autocomplete="off"> - @foreach($locations as $location) - <optgroup label="{{$location->name}}"> - @foreach($location->nodes as $node) - <option @if($product->nodes->contains('id' , $node->id)) selected - @endif value="{{$node->id}}">{{$node->name}}</option> + class="custom-select @error('nodes') is-invalid @enderror" name="nodes[]" + multiple="multiple" autocomplete="off"> + @foreach ($locations as $location) + <optgroup label="{{ $location->name }}"> + @foreach ($location->nodes as $node) + <option @if ($product->nodes->contains('id', $node->id)) selected @endif + value="{{ $node->id }}">{{ $node->name }}</option> @endforeach </optgroup> @endforeach </select> @error('nodes') - <div class="text-danger"> - {{$message}} - </div> + <div class="text-danger"> + {{ $message }} + </div> @enderror <div class="text-muted"> - {{__('This product will only be available for these nodes')}} + {{ __('This product will only be available for these nodes') }} </div> </div> <div class="form-group"> <label for="eggs">Eggs</label> <select id="eggs" style="width:100%" - class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" - multiple="multiple" autocomplete="off"> - @foreach($nests as $nest) - <optgroup label="{{$nest->name}}"> - @foreach($nest->eggs as $egg) - <option @if($product->eggs->contains('id' , $egg->id)) selected - @endif value="{{$egg->id}}">{{$egg->name}}</option> + class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" + multiple="multiple" autocomplete="off"> + @foreach ($nests as $nest) + <optgroup label="{{ $nest->name }}"> + @foreach ($nest->eggs as $egg) + <option @if ($product->eggs->contains('id', $egg->id)) selected @endif + value="{{ $egg->id }}">{{ $egg->name }}</option> @endforeach </optgroup> @endforeach </select> @error('eggs') - <div class="text-danger"> - {{$message}} - </div> + <div class="text-danger"> + {{ $message }} + </div> @enderror <div class="text-muted"> - {{__('This product will only be available for these eggs')}} + {{ __('This product will only be available for these eggs') }} </div> </div> @@ -358,9 +354,8 @@ class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" }) </script> <script> - document.addEventListener('DOMContentLoaded', function () { + document.addEventListener('DOMContentLoaded', function() { $('[data-toggle="popover"]').popover(); }); </script> - @endsection From 67cbde2fa1f6e57894d30e9f9dd040829ac06c3d Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 16 May 2023 21:32:04 +0200 Subject: [PATCH 173/514] ticket category permissions --- app/Http/Controllers/Admin/TicketCategoryController.php | 4 ++-- config/permissions_web.php | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/TicketCategoryController.php b/app/Http/Controllers/Admin/TicketCategoryController.php index 74fff87a7..3d4d8e879 100644 --- a/app/Http/Controllers/Admin/TicketCategoryController.php +++ b/app/Http/Controllers/Admin/TicketCategoryController.php @@ -9,8 +9,8 @@ class TicketCategoryController extends Controller { - const READ_PERMISSION = "admin.tickets.read"; - const WRITE_PERMISSION = "admin.tickets.write"; + const READ_PERMISSION = "admin.tickets.category.read"; + const WRITE_PERMISSION = "admin.tickets.category.write"; /** * * Display a listing of the resource. diff --git a/config/permissions_web.php b/config/permissions_web.php index 0ae789ca7..1c3828f70 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -17,6 +17,9 @@ 'admin.tickets.write', 'admin.tickets.get_notification', + 'admin.tickets.category.read', + 'admin.tickets.category.write', + 'admin.ticket_blacklist.read', 'admin.ticket_blacklist.write', From f37b78d7502eebc52f5cc361beccf29790b0fb90 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 16 May 2023 21:32:04 +0200 Subject: [PATCH 174/514] ticket category permissions --- app/Http/Controllers/Admin/TicketCategoryController.php | 4 ++-- config/permissions_web.php | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/TicketCategoryController.php b/app/Http/Controllers/Admin/TicketCategoryController.php index 74fff87a7..3d4d8e879 100644 --- a/app/Http/Controllers/Admin/TicketCategoryController.php +++ b/app/Http/Controllers/Admin/TicketCategoryController.php @@ -9,8 +9,8 @@ class TicketCategoryController extends Controller { - const READ_PERMISSION = "admin.tickets.read"; - const WRITE_PERMISSION = "admin.tickets.write"; + const READ_PERMISSION = "admin.tickets.category.read"; + const WRITE_PERMISSION = "admin.tickets.category.write"; /** * * Display a listing of the resource. diff --git a/config/permissions_web.php b/config/permissions_web.php index 0ae789ca7..1c3828f70 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -17,6 +17,9 @@ 'admin.tickets.write', 'admin.tickets.get_notification', + 'admin.tickets.category.read', + 'admin.tickets.category.write', + 'admin.ticket_blacklist.read', 'admin.ticket_blacklist.write', From 2c9b933c6d0942888e74205b6593f2cc5e25fc34 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 16 May 2023 21:36:59 +0200 Subject: [PATCH 175/514] fix server upgrade --- app/Http/Controllers/ServerController.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 6324f870e..d6c344f5f 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -389,13 +389,6 @@ public function upgrade(Server $server, Request $request) $server->allocation = $serverAttributes['allocation']; $response = $this->pterodactyl->updateServer($server, $newProduct); if ($response->failed()) return redirect()->route('servers.index')->with('error', __("The system was unable to update your server product. Please try again later or contact support.")); - //update user balance - $user->decrement('credits', $priceupgrade); - //restart the server - $response = $this->pterodactyl->powerAction($server, 'restart'); - if ($response->failed()) { - return redirect()->route('servers.index')->with('error', $response->json()['errors'][0]['detail']); - } // Remove the allocation property from the server object as it is not a column in the database unset($server->allocation); @@ -414,7 +407,7 @@ public function upgrade(Server $server, Request $request) $user->decrement('credits', $newProduct->price); //restart the server - $response = Pterodactyl::powerAction($server, "restart"); + $response = $this->pterodactyl->powerAction($server, 'restart'); if ($response->failed()) return redirect()->route('servers.index')->with('error', 'Server upgraded successfully! Could not restart the server: ' . $response->json()['errors'][0]['detail']); return redirect()->route('servers.show', ['server' => $server->id])->with('success', __('Server Successfully Upgraded')); } else { From 2b0321733e9029ef5c5a01412ec7b08b42eb24a9 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 16 May 2023 21:36:59 +0200 Subject: [PATCH 176/514] fix server upgrade --- app/Http/Controllers/ServerController.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 6324f870e..d6c344f5f 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -389,13 +389,6 @@ public function upgrade(Server $server, Request $request) $server->allocation = $serverAttributes['allocation']; $response = $this->pterodactyl->updateServer($server, $newProduct); if ($response->failed()) return redirect()->route('servers.index')->with('error', __("The system was unable to update your server product. Please try again later or contact support.")); - //update user balance - $user->decrement('credits', $priceupgrade); - //restart the server - $response = $this->pterodactyl->powerAction($server, 'restart'); - if ($response->failed()) { - return redirect()->route('servers.index')->with('error', $response->json()['errors'][0]['detail']); - } // Remove the allocation property from the server object as it is not a column in the database unset($server->allocation); @@ -414,7 +407,7 @@ public function upgrade(Server $server, Request $request) $user->decrement('credits', $newProduct->price); //restart the server - $response = Pterodactyl::powerAction($server, "restart"); + $response = $this->pterodactyl->powerAction($server, 'restart'); if ($response->failed()) return redirect()->route('servers.index')->with('error', 'Server upgraded successfully! Could not restart the server: ' . $response->json()['errors'][0]['detail']); return redirect()->route('servers.show', ['server' => $server->id])->with('success', __('Server Successfully Upgraded')); } else { From fd1ce85723bdf36852021262d307ccc835ffbb03 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 16 May 2023 21:47:34 +0200 Subject: [PATCH 177/514] remove OOM killer from product info --- themes/default/views/servers/create.blade.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index fbc15466d..5b8a3f3c3 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -201,12 +201,6 @@ class="custom-select"> {{ __('Databases') }}</span> <span class="d-inline-block" x-text="product.databases"></span> </li> - <li class="d-flex justify-content-between"> - <span class="d-inline-block"><i class="fas fa-skull-crossbones"></i> - {{ __('OOM Killer') }}</span> - <span class="d-inline-block" - x-text="product.oom_killer == 1 ? 'enabled' : 'disabled'"></span> - </li> <li class="d-flex justify-content-between"> <span class="d-inline-block"><i class="fas fa-network-wired"></i> {{ __('Allocations') }} From dd962a0afbc7014e8efd5addabb1877d768cc99b Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 16 May 2023 21:58:05 +0200 Subject: [PATCH 178/514] fix admin overview total credits counter --- app/Http/Controllers/Admin/OverViewController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/OverViewController.php b/app/Http/Controllers/Admin/OverViewController.php index 90cd9307a..f3d883e87 100644 --- a/app/Http/Controllers/Admin/OverViewController.php +++ b/app/Http/Controllers/Admin/OverViewController.php @@ -38,7 +38,7 @@ public function index(GeneralSettings $general_settings) $counters = collect(); //Set basic variables in the collection $counters->put('users', User::query()->count()); - $counters->put('credits', number_format(User::query()->where('role', '!=', 'admin')->sum('credits'), 2, '.', '')); + $counters->put('credits', number_format(User::query()->whereHas("roles", function($q){ $q->where("id", "!=", "1"); })->sum('credits'), 2, '.', '')); $counters->put('payments', Payment::query()->count()); $counters->put('eggs', Egg::query()->count()); $counters->put('nests', Nest::query()->count()); From 680ffefb1e3cd785f49f938e076ed03ba2cc6bb9 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 16 May 2023 22:03:52 +0200 Subject: [PATCH 179/514] fix server upgrade while installation --- app/Http/Controllers/ServerController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index d6c344f5f..9a00ee1bc 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -389,6 +389,9 @@ public function upgrade(Server $server, Request $request) $server->allocation = $serverAttributes['allocation']; $response = $this->pterodactyl->updateServer($server, $newProduct); if ($response->failed()) return redirect()->route('servers.index')->with('error', __("The system was unable to update your server product. Please try again later or contact support.")); + //restart the server + $response = $this->pterodactyl->powerAction($server, 'restart'); + if ($response->failed()) return redirect()->route('servers.index')->with('error', 'Upgrade Failed! Could not restart the server: ' . $response->json()['errors'][0]['detail']); // Remove the allocation property from the server object as it is not a column in the database unset($server->allocation); @@ -406,9 +409,6 @@ public function upgrade(Server $server, Request $request) // Withdraw the credits for the new product $user->decrement('credits', $newProduct->price); - //restart the server - $response = $this->pterodactyl->powerAction($server, 'restart'); - if ($response->failed()) return redirect()->route('servers.index')->with('error', 'Server upgraded successfully! Could not restart the server: ' . $response->json()['errors'][0]['detail']); return redirect()->route('servers.show', ['server' => $server->id])->with('success', __('Server Successfully Upgraded')); } else { return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('Not Enough Balance for Upgrade')); From 33b402bdeecf31cda7f3b2099378385a09e2ea36 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 17 May 2023 09:15:46 +0200 Subject: [PATCH 180/514] add select 2 to roles-permissions --- themes/default/views/admin/roles/edit.blade.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/themes/default/views/admin/roles/edit.blade.php b/themes/default/views/admin/roles/edit.blade.php index 453233f67..91fef77df 100644 --- a/themes/default/views/admin/roles/edit.blade.php +++ b/themes/default/views/admin/roles/edit.blade.php @@ -58,4 +58,11 @@ </div> </div> + + <script> + document.addEventListener('DOMContentLoaded', (event) => { + $('#permissions').select2(); + }) + </script> @endsection + From 9da53bd02f6a2214cf8acea48ba02734cb08955d Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 17 May 2023 09:44:19 +0200 Subject: [PATCH 181/514] Monthly calculation on server index --- app/Models/Product.php | 22 ++++++++++++++++++-- themes/default/views/servers/index.blade.php | 7 +++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/Models/Product.php b/app/Models/Product.php index 200e9ef8f..f8b5936b3 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -64,9 +64,27 @@ public function getHourlyPrice() } } - public function getDailyPrice() + public function getMonthlyPrice() { - return $this->price / 30; + // calculate the hourly price with the billing period + switch($this->billing_period) { + case 'hourly': + return $this->price * 24 * 30; + case 'daily': + return $this->price * 30; + case 'weekly': + return $this->price * 4; + case 'monthly': + return $this->price; + case 'quarterly': + return $this->price / 3; + case 'half-annually': + return $this->price / 6; + case 'annually': + return $this->price / 12; + default: + return $this->price; + } } public function getWeeklyPrice() diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index dd417eac2..09858c676 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -62,8 +62,8 @@ class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span> </a> <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" aria-labelledby="dropdownMenuLink"> - @if (!empty(config('SETTINGS::MISC:PHPMYADMIN:URL'))) - <a href="{{ config('SETTINGS::MISC:PHPMYADMIN:URL') }}" + @if (!empty($phpmyadmin_url))) + <a href="{{ $phpmyadmin_url }}" class="dropdown-item text-info" target="__blank"><i title="manage" class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span></a> @endif @@ -194,6 +194,9 @@ class="fas fa-info-circle"></i> @elseif($server->product->billing_period == 'hourly') {{ __('per Hour') }} @endif + <i data-toggle="popover" data-trigger="hover" + data-content="{{ __('Your') ." " . $credits_display_name . " ". __('are reduced') ." ". $server->product->billing_period . ". " . __("This however calculates to ") . number_format($server->product->getMonthlyPrice(),2,",",".") . " ". $credits_display_name . " ". __('per Month')}}" + class="fas fa-info-circle"></i> </div> <span> {{ $server->product->price == round($server->product->price) ? round($server->product->price) : $server->product->price }} From da29ead3cd0d274c5f217cfe8b7910db874694bf Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 17 May 2023 09:49:51 +0200 Subject: [PATCH 182/514] fix overview credit erarnings --- app/Http/Controllers/Admin/OverViewController.php | 3 ++- themes/default/views/admin/overview/index.blade.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/OverViewController.php b/app/Http/Controllers/Admin/OverViewController.php index f3d883e87..866d8ece3 100644 --- a/app/Http/Controllers/Admin/OverViewController.php +++ b/app/Http/Controllers/Admin/OverViewController.php @@ -173,7 +173,8 @@ public function index(GeneralSettings $general_settings) $nodeId = $server['attributes']['node']; if ($CPServer = Server::query()->where('pterodactyl_id', $server['attributes']['id'])->first()) { - $price = Product::query()->where('id', $CPServer->product_id)->first()->price; + $product = Product::query()->where('id', $CPServer->product_id)->first(); + $price = $product->getMonthlyPrice(); if (! $CPServer->suspended) { $counters['earnings']->active += $price; $counters['servers']->active++; diff --git a/themes/default/views/admin/overview/index.blade.php b/themes/default/views/admin/overview/index.blade.php index 1e54c6f6a..6b5bd4112 100644 --- a/themes/default/views/admin/overview/index.blade.php +++ b/themes/default/views/admin/overview/index.blade.php @@ -240,7 +240,7 @@ class="fas fa-sync mr-2"></i>{{__('Sync servers')}}</a> <th>{{__('Node')}}</th> <th>{{__('Server count')}}</th> <th>{{__('Resource usage')}}</th> - <th>{{ $credits_display_name . ' ' . __('Usage')}}</th> + <th>{{ $credits_display_name . ' ' . __('Usage') ." (".__('per month').")"}}</th> </tr> </thead> <tbody> From b97bc8edf78e672412522d2acfee07edf49363f3 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 4 Jun 2023 15:41:51 +0200 Subject: [PATCH 183/514] =?UTF-8?q?fix:=20=F0=9F=9A=9A=20Move=20and=20remo?= =?UTF-8?q?ve=20some=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Settings/InvoiceSettings.php | 2 +- app/Settings/MailSettings.php | 6 ------ app/Settings/ServerSettings.php | 7 ------- app/Settings/UserSettings.php | 4 ++-- .../2022_01_05_144858_rename_configurations_table.php | 2 -- .../settings/2023_02_01_181453_create_mail_settings.php | 9 +-------- .../2023_02_01_181950_create_server_settings.php | 8 -------- 7 files changed, 4 insertions(+), 34 deletions(-) diff --git a/app/Settings/InvoiceSettings.php b/app/Settings/InvoiceSettings.php index c7b0068bf..62ba6f1e3 100644 --- a/app/Settings/InvoiceSettings.php +++ b/app/Settings/InvoiceSettings.php @@ -6,13 +6,13 @@ class InvoiceSettings extends Settings { + public bool $enabled; public ?string $company_address; public ?string $company_mail; public ?string $company_name; public ?string $company_phone; public ?string $company_vat; public ?string $company_website; - public bool $enabled; public ?string $prefix; public static function group(): string diff --git a/app/Settings/MailSettings.php b/app/Settings/MailSettings.php index 8b37f632f..1ea8309da 100644 --- a/app/Settings/MailSettings.php +++ b/app/Settings/MailSettings.php @@ -14,7 +14,6 @@ class MailSettings extends Settings public ?string $mail_from_address; public ?string $mail_from_name; public ?string $mail_mailer; - public bool $mail_enabled; public static function group(): string { @@ -52,7 +51,6 @@ public static function getValidations() 'mail_from_address' => 'nullable|string', 'mail_from_name' => 'nullable|string', 'mail_mailer' => 'nullable|string', - 'mail_enabled' => 'nullable|string', ]; } @@ -105,10 +103,6 @@ public static function getOptionInputData() 'type' => 'string', 'description' => 'The mailer of your mail server.', ], - 'mail_enabled' => [ - 'label' => 'Mail Enabled', - 'type' => 'boolean', - ], ]; } } diff --git a/app/Settings/ServerSettings.php b/app/Settings/ServerSettings.php index f56418765..3043066ad 100644 --- a/app/Settings/ServerSettings.php +++ b/app/Settings/ServerSettings.php @@ -9,7 +9,6 @@ class ServerSettings extends Settings public int $allocation_limit; public bool $creation_enabled; public bool $enable_upgrade; - public bool $charge_first_hour; public static function group(): string { @@ -26,7 +25,6 @@ public static function getValidations() 'allocation_limit' => 'required|integer|min:0', 'creation_enabled' => 'nullable|string', 'enable_upgrade' => 'nullable|string', - 'charge_first_hour' => 'nullable|string', ]; } @@ -54,11 +52,6 @@ public static function getOptionInputData() 'type' => 'boolean', 'description' => 'Whether or not users can upgrade their servers.', ], - 'charge_first_hour' => [ - 'label' => 'Charge First Hour', - 'type' => 'boolean', - 'description' => 'Whether or not the first hour of a server is charged.', - ], ]; } } diff --git a/app/Settings/UserSettings.php b/app/Settings/UserSettings.php index 5c1b65dba..df85053eb 100644 --- a/app/Settings/UserSettings.php +++ b/app/Settings/UserSettings.php @@ -6,6 +6,8 @@ class UserSettings extends Settings { + public bool $register_ip_check; + public bool $creation_enabled; public float $credits_reward_after_verify_discord; public float $credits_reward_after_verify_email; public bool $force_discord_verification; @@ -16,8 +18,6 @@ class UserSettings extends Settings public int $server_limit_after_irl_purchase; public int $server_limit_after_verify_discord; public int $server_limit_after_verify_email; - public bool $register_ip_check; - public bool $creation_enabled; public static function group(): string { diff --git a/database/migrations/2022_01_05_144858_rename_configurations_table.php b/database/migrations/2022_01_05_144858_rename_configurations_table.php index 4785ed01e..1090a0223 100644 --- a/database/migrations/2022_01_05_144858_rename_configurations_table.php +++ b/database/migrations/2022_01_05_144858_rename_configurations_table.php @@ -28,7 +28,6 @@ public function up() DB::table('settings')->where('key', 'REGISTER_IP_CHECK')->update(['key' => 'SETTINGS::SYSTEM:REGISTER_IP_CHECK']); DB::table('settings')->where('key', 'CREDITS_DISPLAY_NAME')->update(['key' => 'SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME']); DB::table('settings')->where('key', 'ALLOCATION_LIMIT')->update(['key' => 'SETTINGS::SERVER:ALLOCATION_LIMIT']); - DB::table('settings')->where('key', 'SERVER_CREATE_CHARGE_FIRST_HOUR')->update(['key' => 'SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR']); DB::table('settings')->where('key', 'SALES_TAX')->update(['key' => 'SETTINGS::PAYMENTS:SALES_TAX']); } @@ -52,7 +51,6 @@ public function down() DB::table('configurations')->where('key', 'SETTINGS::USER:FORCE_EMAIL_VERIFICATION')->update(['key' => 'FORCE_EMAIL_VERIFICATION']); DB::table('configurations')->where('key', 'SETTINGS::USER:FORCE_DISCORD_VERIFICATION')->update(['key' => 'FORCE_DISCORD_VERIFICATION']); DB::table('configurations')->where('key', 'SETTINGS::SYSTEM:REGISTER_IP_CHECK')->update(['key' => 'REGISTER_IP_CHECK']); - DB::table('configurations')->where('key', 'SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR')->update(['key' => 'SERVER_CREATE_CHARGE_FIRST_HOUR']); DB::table('configurations')->where('key', 'SETTINGS::SERVER:ALLOCATION_LIMIT')->update(['key' => 'ALLOCATION_LIMIT']); DB::table('configurations')->where('key', 'SETTINGS::SERVER:CREDITS_DISPLAY_NAME')->update(['key' => 'SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME']); DB::table('configurations')->where('key', 'SETTINGS::PAYMENTS:SALES_TAX')->update(['key' => 'SALES_TAX']); diff --git a/database/settings/2023_02_01_181453_create_mail_settings.php b/database/settings/2023_02_01_181453_create_mail_settings.php index cc609b44a..56953b780 100644 --- a/database/settings/2023_02_01_181453_create_mail_settings.php +++ b/database/settings/2023_02_01_181453_create_mail_settings.php @@ -18,7 +18,6 @@ public function up(): void $this->migrator->add('mail.mail_from_address', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_ADDRESS') : env('MAIL_FROM_ADDRESS', 'example@example.com')); $this->migrator->add('mail.mail_from_name', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_NAME') : env('APP_NAME', 'CtrlPanel.gg')); $this->migrator->add('mail.mail_mailer', $table_exists ? $this->getOldValue('SETTINGS::MAIL:MAILER') : env('MAIL_MAILER', 'smtp')); - $this->migrator->add('mail.mail_enabled', true); } public function down(): void @@ -72,12 +71,7 @@ public function down(): void 'type' => 'string', 'description' => 'The mailer of the mail server.', ], - [ - 'key' => 'SETTINGS::MAIL:ENABLED', - 'value' => $this->getNewValue('mail_enabled'), - 'type' => 'boolean', - 'description' => 'The enabled state of the mail server.', - ], + ]); $this->migrator->delete('mail.mail_host'); @@ -88,7 +82,6 @@ public function down(): void $this->migrator->delete('mail.mail_from_address'); $this->migrator->delete('mail.mail_from_name'); $this->migrator->delete('mail.mail_mailer'); - $this->migrator->delete('mail.mail_enabled'); } diff --git a/database/settings/2023_02_01_181950_create_server_settings.php b/database/settings/2023_02_01_181950_create_server_settings.php index 8f9c38124..7198adcb6 100644 --- a/database/settings/2023_02_01_181950_create_server_settings.php +++ b/database/settings/2023_02_01_181950_create_server_settings.php @@ -13,7 +13,6 @@ public function up(): void $this->migrator->add('server.allocation_limit', $table_exists ? $this->getOldValue('SETTINGS::SERVER:ALLOCATION_LIMIT') : 200); $this->migrator->add('server.creation_enabled', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS') : true); $this->migrator->add('server.enable_upgrade', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:ENABLE_UPGRADE') : false); - $this->migrator->add('server.charge_first_hour', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR') : false); } public function down(): void @@ -37,18 +36,11 @@ public function down(): void 'type' => 'boolean', 'description' => 'Whether or not users can upgrade their servers.', ], - [ - 'key' => 'SETTINGS::SYSTEM:SERVER_CREATE_CHARGE_FIRST_HOUR', - 'value' => $this->getNewValue('charge_first_hour'), - 'type' => 'boolean', - 'description' => 'Whether or not to charge the user for the first hour of their server.', - ], ]); $this->migrator->delete('server.allocation_limit'); $this->migrator->delete('server.creation_enabled'); $this->migrator->delete('server.enable_upgrade'); - $this->migrator->delete('server.charge_first_hour'); } public function getNewValue(string $name) From 83e153058cf69a28fa478c092c2563e6342dd87d Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 4 Jun 2023 16:08:22 +0200 Subject: [PATCH 184/514] =?UTF-8?q?fix:=20=F0=9F=90=9Blocale=20default=20l?= =?UTF-8?q?anguage=20selection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Admin/SettingsController.php | 5 +- app/Settings/LocaleSettings.php | 1 + .../views/admin/settings/index.blade.php | 227 +++++++++--------- 3 files changed, 114 insertions(+), 119 deletions(-) diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 47cc4207d..988603220 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -64,6 +64,7 @@ public function index() 'type' => $optionInputData[$key]['type'] ?? 'string', 'description' => $optionInputData[$key]['description'] ?? '', 'options' => $optionInputData[$key]['options'] ?? [], + 'identifier' => $optionInputData[$key]['identifier'] ?? 'option' ]; } @@ -96,7 +97,7 @@ public function update(Request $request) { $category = request()->get('category'); - $this->checkPermission("settings.".strtolower($category).".write"); + $this->checkPermission("settings." . strtolower($category) . ".write"); $settings_class = request()->get('settings_class'); @@ -125,7 +126,7 @@ public function update(Request $request) continue; } if ($rp->name == 'available') { - $settingsClass->$key = implode(",",$request->$key); + $settingsClass->$key = implode(",", $request->$key); continue; } diff --git a/app/Settings/LocaleSettings.php b/app/Settings/LocaleSettings.php index 61934bc17..81cf2b642 100644 --- a/app/Settings/LocaleSettings.php +++ b/app/Settings/LocaleSettings.php @@ -62,6 +62,7 @@ public static function getOptionInputData() 'type' => 'select', 'description' => 'The default locale to use.', 'options' => config('app.available_locales'), + 'identifier' => 'display' ], 'dynamic' => [ 'label' => 'Dynamic Locale', diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 006e1f4cb..65d552e88 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -12,7 +12,7 @@ <ol class="breadcrumb float-sm-right"> <li class="breadcrumb-item"><a href="">{{ __('Dashboard') }}</a></li> <li class="breadcrumb-item"><a class="text-muted" - href="{{ route('admin.settings.index') }}">{{ __('Settings') }}</a> + href="{{ route('admin.settings.index') }}">{{ __('Settings') }}</a> </li> </ol> </div> @@ -48,24 +48,21 @@ <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" data-accordion="false"> <li class="nav-item border-bottom-0"> - <a href="#icons" - class="nav-link" data-toggle="pill" - role="tab"> - <i - class="nav-icon fas fa-image"></i> + <a href="#icons" class="nav-link" data-toggle="pill" role="tab"> + <i class="nav-icon fas fa-image"></i> <p> - {{ __("Images / Icons") }} + {{ __('Images / Icons') }} </p> </a> </li> @foreach ($settings as $category => $options) - @if(!str_contains($options['settings_class'],"Extension")) - @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) + @if (!str_contains($options['settings_class'], 'Extension')) + @canany(['settings.' . strtolower($category) . '.read', 'settings.' . + strtolower($category) . '.write']) <li class="nav-item border-bottom-0"> <a href="#{{ $category }}" - class="nav-link {{ $loop->first ? 'active' : '' }}" - data-toggle="pill" - role="tab"> + class="nav-link {{ $loop->first ? 'active' : '' }}" data-toggle="pill" + role="tab"> <i class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> <p> @@ -79,35 +76,34 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> </ul> - <button class="btn btn-outline-secondary" type="button" data-toggle="collapse" - data-target="#collapseExtensions" aria-expanded="false" - aria-controls="collapseExtensions"> - {{__("Extension Settings")}} - </button> + <button class="btn btn-outline-secondary" type="button" data-toggle="collapse" + data-target="#collapseExtensions" aria-expanded="false" + aria-controls="collapseExtensions"> + {{ __('Extension Settings') }} + </button> - <div class="collapse" id="collapseExtensions"> - <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" - data-accordion="false"> + <div class="collapse" id="collapseExtensions"> + <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" + data-accordion="false"> @foreach ($settings as $category => $options) - @if(str_contains($options['settings_class'],"Extension")) - - @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) - <li class="nav-item border-bottom-0"> - <a href="#{{ $category }}" - class="nav-link" - data-toggle="pill" - role="tab"> - <i class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> - <p> - {{ $category }} - </p> - </a> - </li> + @if (str_contains($options['settings_class'], 'Extension')) + @canany(['settings.' . strtolower($category) . '.read', 'settings.' . + strtolower($category) . '.write']) + <li class="nav-item border-bottom-0"> + <a href="#{{ $category }}" class="nav-link" data-toggle="pill" + role="tab"> + <i + class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> + <p> + {{ $category }} + </p> + </a> + </li> @endcanany @endif @endforeach - </div> + </div> </ul> </nav> </div> @@ -115,50 +111,44 @@ class="nav-link" <!-- Content in $settings --> <div class="col-10 p-0"> <div class="tab-content ml-3" style="width: 100%;"> - <div container class="tab-pane fade container" - id="icons" role="tabpanel"> + <div container class="tab-pane fade container" id="icons" role="tabpanel"> <form method="POST" enctype="multipart/form-data" class="mb-3" - action="{{ route('admin.settings.updateIcons') }}"> + action="{{ route('admin.settings.updateIcons') }}"> @csrf @method('POST') <div class="row"> <div class="card ml-5" style="width: 18rem;"> - <span class="h3 text-center">{{__("FavIcon")}} </span> + <span class="h3 text-center">{{ __('FavIcon') }} </span> <div class="card-body"> </div> <input type="file" accept="image/x-icon" class="form-control" - name="favicon" - id="favicon"> + name="favicon" id="favicon"> </div> <div class="card ml-5" style="width: 18rem;"> - <span class="h3 text-center">{{__("Icon")}} </span> - <img - src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" + <span class="h3 text-center">{{ __('Icon') }} </span> + <img src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..."> <div class="card-body"> </div> <input type="file" accept="image/png,image/jpeg,image/jpg" - class="form-control" - name="icon" id="icon"> + class="form-control" name="icon" id="icon"> </div> <div class="card ml-5" style="width: 18rem;"> - <span class="h3 text-center">{{__("Login-page Logo")}} </span> - <img - src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" + <span class="h3 text-center">{{ __('Login-page Logo') }} </span> + <img src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..."> <div class="card-body"> </div> <input type="file" accept="image/png,image/jpeg,image/jpg" - class="form-control" - name="logo" id="logo"> + class="form-control" name="logo" id="logo"> </div> </div> <div class="row"> @@ -167,16 +157,16 @@ class="form-control" </form> </div> @foreach ($settings as $category => $options) - @canany(["settings.".strtolower($category).".read","settings.".strtolower($category).".write"]) - <div - class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" - id="{{ $category }}" role="tabpanel"> + @canany(['settings.' . strtolower($category) . '.read', 'settings.' . + strtolower($category) . '.write']) + <div class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" + id="{{ $category }}" role="tabpanel"> <form action="{{ route('admin.settings.update') }}" method="POST"> @csrf @method('POST') <input type="hidden" name="settings_class" - value="{{ $options['settings_class'] }}"> + value="{{ $options['settings_class'] }}"> <input type="hidden" name="category" value="{{ $category }}"> @foreach ($options as $key => $value) @@ -191,11 +181,10 @@ class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" <div class="col-8"> <div class="custom-control mb-3 d-flex align-items-center"> @if ($value['description']) - <i class="fas fa-info-circle mr-4" - data-toggle="popover" - data-trigger="hover" data-placement="top" - data-html="true" - data-content="{{ $value['description'] }}"></i> + <i class="fas fa-info-circle mr-4" data-toggle="popover" + data-trigger="hover" data-placement="top" + data-html="true" + data-content="{{ $value['description'] }}"></i> @else <i class="fas fa-info-circle mr-4 invisible"></i> @endif @@ -204,62 +193,68 @@ class="tab-pane fade container {{ $loop->first ? 'active show' : '' }}" @switch($value) @case($value['type'] == 'string') <input type="text" class="form-control" - name="{{ $key }}" - value="{{ $value['value'] }}"> - @break + name="{{ $key }}" + value="{{ $value['value'] }}"> + @break @case($value['type'] == 'boolean') <input type="checkbox" name="{{ $key }}" - value="{{ $value['value'] }}" + value="{{ $value['value'] }}" {{ $value['value'] ? 'checked' : '' }}> - @break + @break @case($value['type'] == 'number') <input type="number" class="form-control" - name="{{ $key }}" - value="{{ $value['value'] }}"> - @break + name="{{ $key }}" + value="{{ $value['value'] }}"> + @break @case($value['type'] == 'select') <select id="{{ $key }}" - class="custom-select w-100" - name="{{ $key }}"> - - @foreach ($value['options'] as $option=>$display) - <option value="{{ $option }}" - {{ $value['value'] == $option ? 'selected' : '' }}> - {{ __($display) }} - </option> - @endforeach + class="custom-select w-100" + name="{{ $key }}"> + @if ($value['identifier'] == 'display') + {{ error_log($key . 'True, IAM A DISPLAY') }} + @foreach ($value['options'] as $option => $display) + <option value="{{ $display }}" + {{ $value['value'] == $display ? 'selected' : '' }}> + {{ __($display) }} + </option> + @endforeach + @else + @foreach ($value['options'] as $option => $display) + <option value="{{ $option }}" + {{ $value['value'] == $option ? 'selected' : '' }}> + {{ __($display) }} + </option> + @endforeach + @endif </select> - @break + @break @case($value['type'] == 'multiselect') <select id="{{ $key }}" - class="custom-select w-100" - name="{{ $key }}[]" - multiple> + class="custom-select w-100" + name="{{ $key }}[]" multiple> @foreach ($value['options'] as $option) <option value="{{ $option }}" - {{ strpos($value['value'],$option) !== false ? 'selected' : '' }}> + {{ strpos($value['value'], $option) !== false ? 'selected' : '' }}> {{ __($option) }} </option> @endforeach </select> - @break + @break @case($value['type'] == 'textarea') - <textarea class="form-control" - name="{{ $key }}" - rows="3">{{ $value['value'] }}</textarea> - @break + <textarea class="form-control" name="{{ $key }}" rows="3">{{ $value['value'] }}</textarea> + @break @default @endswitch @error($key) - <div class="text-danger "> - {{ $message }} - </div> + <div class="text-danger "> + {{ $message }} + </div> @enderror </div> @@ -268,41 +263,39 @@ class="custom-select w-100" </div> </div> - @endforeach <!-- TODO: Display this only on the General tab - <div class="row"> - <div class="col-4 d-flex align-items-center"> - <label for="recaptcha_preview">{{__("ReCAPTCHA Preview")}}</label> - </div> - - <div class="col-8"> - - <div class="w-100"> - <div class="input-group mb-3"> - {!! htmlScriptTagJsApi() !!} - {!! htmlFormSnippet() !!} - @error('g-recaptcha-response') - <span class="text-danger" role="alert"> - <small><strong>{{ $message }}</strong></small> - </span> - @enderror - </div> - </div> - </div> - </div> - --> + <div class="row"> + <div class="col-4 d-flex align-items-center"> + <label for="recaptcha_preview">{{ __('ReCAPTCHA Preview') }}</label> + </div> + + <div class="col-8"> + + <div class="w-100"> + <div class="input-group mb-3"> + {!! htmlScriptTagJsApi() !!} + {!! htmlFormSnippet() !!} + @error('g-recaptcha-response') + <span class="text-danger" role="alert"> + <small><strong>{{ $message }}</strong></small> + </span> + @enderror + </div> + </div> + </div> + </div> + --> <div class="row"> <div class="col-12 d-flex align-items-center justify-content-end"> - <button type="submit" - class="btn btn-primary float-right ">Save + <button type="submit" class="btn btn-primary float-right ">Save </button> <button type="reset" - class="btn btn-secondary float-right ml-2">Reset + class="btn btn-secondary float-right ml-2">Reset </button> </div> </div> @@ -331,7 +324,7 @@ class="btn btn-secondary float-right ml-2">Reset $('.nav-item a[href="' + tabPaneHash + '"]').tab('show'); } - $('.nav-pills a').click(function (e) { + $('.nav-pills a').click(function(e) { $(this).tab('show'); const scrollmem = $('body').scrollTop(); window.location.hash = this.hash; From 957da3dc786b523cc8bdb9ed7b6976a86c27450f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 4 Jun 2023 16:08:47 +0200 Subject: [PATCH 185/514] =?UTF-8?q?chore:=20=F0=9F=94=A5=20remove=20error?= =?UTF-8?q?=5Flog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/admin/settings/index.blade.php | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 65d552e88..dce0bbb30 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -214,7 +214,6 @@ class="form-control" name="logo" id="logo"> class="custom-select w-100" name="{{ $key }}"> @if ($value['identifier'] == 'display') - {{ error_log($key . 'True, IAM A DISPLAY') }} @foreach ($value['options'] as $option => $display) <option value="{{ $display }}" {{ $value['value'] == $display ? 'selected' : '' }}> @@ -267,27 +266,27 @@ class="custom-select w-100" <!-- TODO: Display this only on the General tab - <div class="row"> - <div class="col-4 d-flex align-items-center"> - <label for="recaptcha_preview">{{ __('ReCAPTCHA Preview') }}</label> - </div> + <div class="row"> + <div class="col-4 d-flex align-items-center"> + <label for="recaptcha_preview">{{ __('ReCAPTCHA Preview') }}</label> + </div> - <div class="col-8"> + <div class="col-8"> - <div class="w-100"> - <div class="input-group mb-3"> - {!! htmlScriptTagJsApi() !!} - {!! htmlFormSnippet() !!} - @error('g-recaptcha-response') + <div class="w-100"> + <div class="input-group mb-3"> + {!! htmlScriptTagJsApi() !!} + {!! htmlFormSnippet() !!} + @error('g-recaptcha-response') <span class="text-danger" role="alert"> - <small><strong>{{ $message }}</strong></small> - </span> + <small><strong>{{ $message }}</strong></small> + </span> @enderror - </div> </div> - </div> - </div> - --> + </div> + </div> + </div> + --> <div class="row"> From 820f47738d1e8988075fb6212854161ee9167c35 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 4 Jun 2023 16:10:09 +0200 Subject: [PATCH 186/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Settings/ReferralSettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Settings/ReferralSettings.php b/app/Settings/ReferralSettings.php index f56b71883..b08923fde 100644 --- a/app/Settings/ReferralSettings.php +++ b/app/Settings/ReferralSettings.php @@ -27,7 +27,7 @@ public static function getValidations() 'always_give_commission' => 'nullable|string', 'enabled' => 'nullable|string', 'reward' => 'nullable|numeric', - 'mode' => 'required|in:comission,sign-up,both', + 'mode' => 'required|in:commission,sign-up,both', 'percentage' => 'nullable|numeric', ]; } From e8a6adb4e6ad93c7bdfd54ab2aae4a365b6688da Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Thu, 8 Jun 2023 16:10:42 +0200 Subject: [PATCH 187/514] =?UTF-8?q?fix:=20=F0=9F=9A=9A=20Typo=20cancelled?= =?UTF-8?q?=20->=20canceled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/ChargeServers.php | 6 +++--- app/Http/Controllers/Admin/ServerController.php | 14 +++++++------- app/Http/Controllers/ServerController.php | 6 +++--- app/Models/Server.php | 2 +- app/Models/User.php | 14 ++++++-------- ..._08_234527_add_cancelation_to_servers_table.php | 5 ++++- themes/default/views/servers/index.blade.php | 6 +++--- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 014af9636..8c7b9d4de 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -24,7 +24,7 @@ class ChargeServers extends Command */ protected $description = 'Charge all users with severs that are due to be charged'; - /** + /** * A list of users that have to be notified * @var array */ @@ -60,7 +60,7 @@ public function handle() // check if server is due to be charged by comparing its last_billed date with the current date and the billing period $newBillingDate = null; - switch($billing_period) { + switch ($billing_period) { case 'annually': $newBillingDate = Carbon::parse($server->last_billed)->addYear(); break; @@ -91,7 +91,7 @@ public function handle() } // check if the server is canceled or if user has enough credits to charge the server or - if ( $server->cancelled || $user->credits <= $product->price) { + if ($server->canceled || $user->credits <= $product->price) { try { // suspend server $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php index 9efd70f01..97ee15535 100644 --- a/app/Http/Controllers/Admin/ServerController.php +++ b/app/Http/Controllers/Admin/ServerController.php @@ -25,7 +25,7 @@ class ServerController extends Controller const WRITE_PERMISSION = "admin.servers.write"; const SUSPEND_PERMISSION = "admin.servers.suspend"; const CHANGEOWNER_PERMISSION = "admin.servers.write.owner"; - const CHANGE_IDENTIFIER_PERMISSION ="admin.servers.write.identifier"; + const CHANGE_IDENTIFIER_PERMISSION = "admin.servers.write.identifier"; const DELETE_PERMISSION = "admin.servers.delete"; private $pterodactyl; @@ -100,7 +100,7 @@ public function update(Request $request, Server $server) } // update the identifier - if($this->can(self::CHANGE_IDENTIFIER_PERMISSION)) { + if ($this->can(self::CHANGE_IDENTIFIER_PERMISSION)) { $server->identifier = $request->get('identifier'); } @@ -133,13 +133,13 @@ public function destroy(Server $server) * @param Server $server * @return RedirectResponse|Response */ - public function cancel (Server $server) + public function cancel(Server $server) { try { - error_log($server->update([ - 'cancelled' => now(), - ])); - return redirect()->route('servers.index')->with('success', __('Server cancelled')); + $server->update([ + 'canceled' => now(), + ]); + return redirect()->route('servers.index')->with('success', __('Server canceled')); } catch (Exception $e) { return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to cancel the server"') . $e->getMessage() . '"'); } diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 5380e10b8..d291e2986 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -281,9 +281,9 @@ public function cancel(Server $server) } try { $server->update([ - 'cancelled' => now(), + 'canceled' => now(), ]); - return redirect()->route('servers.index')->with('success', __('Server cancelled')); + return redirect()->route('servers.index')->with('success', __('Server canceled')); } catch (Exception $e) { return redirect()->route('servers.index')->with('error', __('An exception has occurred while trying to cancel the server"') . $e->getMessage() . '"'); } @@ -401,7 +401,7 @@ public function upgrade(Server $server, Request $request) 'product_id' => $newProduct->id, 'updated_at' => now(), 'last_billed' => now(), - 'cancelled' => null, + 'canceled' => null, ]); // Refund the user the overpayed credits diff --git a/app/Models/Server.php b/app/Models/Server.php index 07acc08a9..bd70c5367 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -60,7 +60,7 @@ public function getActivitylogOptions(): LogOptions "product_id", "pterodactyl_id", "last_billed", - "cancelled" + "canceled" ]; /** diff --git a/app/Models/User.php b/app/Models/User.php index 6d14f585a..2c632e3cd 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -98,8 +98,6 @@ public function __construct() $ptero_settings = new PterodactylSettings(); $this->pterodactyl = new PterodactylClient($ptero_settings); - - } public static function boot() @@ -252,7 +250,7 @@ private function getServersWithProduct() { return $this->servers() ->whereNull('suspended') - ->whereNull('cancelled') + ->whereNull('canceled') ->with('product') ->get(); } @@ -289,15 +287,15 @@ public function reVerifyEmail() ])->save(); } - public function referredBy(){ - $referee = DB::table('user_referrals')->where("registered_user_id",$this->id)->first(); + public function referredBy() + { + $referee = DB::table('user_referrals')->where("registered_user_id", $this->id)->first(); - if($referee){ - $referee = User::where("id",$referee->referral_id)->firstOrFail(); + if ($referee) { + $referee = User::where("id", $referee->referral_id)->firstOrFail(); return $referee; } return Null; - } public function getActivitylogOptions(): LogOptions diff --git a/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php b/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php index 0d0f4e203..b26b19cc5 100644 --- a/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php +++ b/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php @@ -15,6 +15,9 @@ public function up() { // User already has installed the addon before if (Schema::hasColumn("servers", "cancelled")) { + Schema::table('servers', function (Blueprint $table) { + $table->renameColumn('cancelled', 'canceled'); + }); return; } @@ -31,7 +34,7 @@ public function up() public function down() { Schema::table('servers', function (Blueprint $table) { - $table->dropColumn('cancelled'); + $table->dropColumn('canceled'); }); } } diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index 09858c676..f7c2cec5b 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -82,8 +82,8 @@ class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') } <div class="col-7 my-auto"> @if($server->suspended) <span class="badge badge-danger">{{ __('Suspended') }}</span> - @elseif($server->cancelled) - <span class="badge badge-warning">{{ __('Cancelled') }}</span> + @elseif($server->canceled) + <span class="badge badge-warning">{{ __('Canceled') }}</span> @else <span class="badge badge-success">{{ __('Active') }}</span> @endif @@ -220,7 +220,7 @@ class="btn btn-info text-center mr-3" </a> <button onclick="handleServerCancel('{{ $server->id }}');" target="__blank" class="btn btn-warning text-center" - {{ $server->suspended || $server->cancelled ? "disabled" : "" }} + {{ $server->suspended || $server->canceled ? "disabled" : "" }} data-toggle="tooltip" data-placement="bottom" title="{{ __('Cancel Server') }}"> <i class="fas fa-ban mx-2"></i> </button> From e03ac5dae0097b999104450bf8751ca904cf2d2a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Thu, 8 Jun 2023 20:23:06 +0200 Subject: [PATCH 188/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20missed=20one=20li?= =?UTF-8?q?ne=20for=20typo=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2023_05_08_234527_add_cancelation_to_servers_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php b/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php index b26b19cc5..94efae378 100644 --- a/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php +++ b/database/migrations/2023_05_08_234527_add_cancelation_to_servers_table.php @@ -22,7 +22,7 @@ public function up() } Schema::table('servers', function (Blueprint $table) { - $table->dateTime('cancelled')->nullable(); + $table->dateTime('canceled')->nullable(); }); } From 72f3decd3f97b9e1236b7cd42ead513b8eb624ba Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Wed, 17 May 2023 16:17:51 +0000 Subject: [PATCH 189/514] Feat: Let's implement the coupons now --- .editorconfig | 25 +- .gitignore | 1 + .../PayPal/PayPalExtension.php | 25 +- .../Controllers/Admin/CouponController.php | 159 ++++++++++ .../Controllers/Admin/PaymentController.php | 30 ++ app/Http/Controllers/ProfileController.php | 3 +- app/Models/Coupon.php | 162 ++++++++++ app/Models/User.php | 10 +- app/Settings/CouponSettings.php | 43 +++ app/Traits/Coupon.php | 86 ++++++ bootstrap/cache/.gitignore | 0 config/permissions_web.php | 3 + config/settings.php | 2 + ...2023_05_11_153719_create_coupons_table.php | 37 +++ ...05_14_152604_create_user_coupons_table.php | 32 ++ ...23_05_12_170041_create_coupon_settings.php | 16 + package-lock.json | 2 +- routes/web.php | 5 + storage/app/.gitignore | 0 storage/app/public/.gitignore | 0 storage/debugbar/.gitignore | 0 storage/framework/.gitignore | 0 storage/framework/cache/.gitignore | 0 storage/framework/sessions/.gitignore | 0 storage/framework/testing/.gitignore | 0 storage/framework/views/.gitignore | 0 storage/logs/.gitignore | 0 .../views/admin/coupons/create.blade.php | 289 ++++++++++++++++++ .../views/admin/coupons/index.blade.php | 65 ++++ themes/default/views/layouts/main.blade.php | 14 +- themes/default/views/store/checkout.blade.php | 128 +++++++- 31 files changed, 1122 insertions(+), 15 deletions(-) create mode 100644 app/Http/Controllers/Admin/CouponController.php create mode 100644 app/Models/Coupon.php create mode 100644 app/Settings/CouponSettings.php create mode 100644 app/Traits/Coupon.php mode change 100644 => 100755 bootstrap/cache/.gitignore create mode 100644 database/migrations/2023_05_11_153719_create_coupons_table.php create mode 100644 database/migrations/2023_05_14_152604_create_user_coupons_table.php create mode 100644 database/settings/2023_05_12_170041_create_coupon_settings.php mode change 100644 => 100755 storage/app/.gitignore mode change 100644 => 100755 storage/app/public/.gitignore mode change 100644 => 100755 storage/debugbar/.gitignore mode change 100644 => 100755 storage/framework/.gitignore mode change 100644 => 100755 storage/framework/cache/.gitignore mode change 100644 => 100755 storage/framework/sessions/.gitignore mode change 100644 => 100755 storage/framework/testing/.gitignore mode change 100644 => 100755 storage/framework/views/.gitignore mode change 100644 => 100755 storage/logs/.gitignore create mode 100644 themes/default/views/admin/coupons/create.blade.php create mode 100644 themes/default/views/admin/coupons/index.blade.php diff --git a/.editorconfig b/.editorconfig index 6537ca467..78326873f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,9 +3,9 @@ root = true [*] charset = utf-8 end_of_line = lf -insert_final_newline = true +indent_size = 2 indent_style = space -indent_size = 4 +insert_final_newline = true trim_trailing_whitespace = true [*.md] @@ -13,3 +13,24 @@ trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 + +[docker-compose.yml] +indent_size = 2 + +[*.php] +indent_size = 4 + +[*.blade.php] +indent_size = 2 + +[*.js] +indent_size = 4 + +[*.jsx] +indent_size = 2 + +[*.tsx] +indent_size = 2 + +[*.json] +indent_size = 4 diff --git a/.gitignore b/.gitignore index 58212a1db..ad9d095b8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ storage/debugbar .env.backup .idea .phpunit.result.cache +.editorconfig docker-compose.override.yml Homestead.json Homestead.yaml diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index 6b1c335e8..73d632df7 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -10,6 +10,7 @@ use App\Models\Payment; use App\Models\ShopProduct; use App\Models\User; +use App\Models\Coupon; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redirect; @@ -41,6 +42,17 @@ static function PaypalPay(Request $request): void $user = Auth::user(); $shopProduct = ShopProduct::findOrFail($request->shopProduct); $discount = PartnerDiscount::getDiscount(); + $discountPrice = $request->get('discountPrice'); + + dd($discountPrice); + + // Partner Discount. + $price = $shopProduct->price - ($shopProduct->price * $discount / 100); + + // Coupon Discount. + // if ($discountPrice) { + // $price = $price - ($price * floatval($coupon_percentage) / 100); + // } // create a new payment $payment = Payment::create([ @@ -50,7 +62,7 @@ static function PaypalPay(Request $request): void 'type' => $shopProduct->type, 'status' => 'open', 'amount' => $shopProduct->quantity, - 'price' => $shopProduct->price - ($shopProduct->price * $discount / 100), + 'price' => $price, 'tax_value' => $shopProduct->getTaxValue(), 'tax_percent' => $shopProduct->getTaxPercent(), 'total_price' => $shopProduct->getTotalPrice(), @@ -73,7 +85,7 @@ static function PaypalPay(Request $request): void 'item_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), - 'value' => $shopProduct->getPriceAfterDiscount(), + 'value' => number_format($price, 2), ], 'tax_total' => [ @@ -86,7 +98,7 @@ static function PaypalPay(Request $request): void ], "application_context" => [ "cancel_url" => route('payment.Cancel'), - "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id]), + "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id, 'couponCode' => $coupon_code]), 'brand_name' => config('app.name', 'CtrlPanel.GG'), 'shipping_preference' => 'NO_SHIPPING' ] @@ -126,6 +138,7 @@ static function PaypalSuccess(Request $laravelRequest): void $payment = Payment::findOrFail($laravelRequest->payment); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); + $coupon_code = $laravelRequest->input('couponCode'); $request = new OrdersCaptureRequest($laravelRequest->input('token')); $request->prefer('return=representation'); @@ -140,6 +153,12 @@ static function PaypalSuccess(Request $laravelRequest): void 'payment_id' => $response->result->id, ]); + // increase the use of the coupon when the payment is confirmed. + if ($coupon_code) { + $coupon = new Coupon; + $coupon->incrementUses($coupon_code); + } + event(new UserUpdateCreditsEvent($user)); event(new PaymentEvent($user, $payment, $shopProduct)); diff --git a/app/Http/Controllers/Admin/CouponController.php b/app/Http/Controllers/Admin/CouponController.php new file mode 100644 index 000000000..44c3d6181 --- /dev/null +++ b/app/Http/Controllers/Admin/CouponController.php @@ -0,0 +1,159 @@ +<?php + +namespace App\Http\Controllers\Admin; + +use App\Http\Controllers\Controller; +use App\Models\Coupon; +use App\Traits\Coupon as CouponTrait; +use Illuminate\Http\Request; +use Carbon\Carbon; + +class CouponController extends Controller +{ + const READ_PERMISSION = "admin.coupons.read"; + const WRITE_PERMISSION = "admin.coupons.write"; + + use CouponTrait; + + /** + * Display a listing of the resource. + * + * @return \Illuminate\Http\Response + */ + public function index() + { + $this->checkPermission(self::READ_PERMISSION); + + return view('admin.coupons.index'); + } + + /** + * Show the form for creating a new resource. + * + * @return \Illuminate\Http\Response + */ + public function create() + { + $this->checkPermission(self::WRITE_PERMISSION); + + return view('admin.coupons.create'); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function store(Request $request) + { + $coupon_code = $request->input('coupon_code'); + $coupon_type = $request->input('coupon_type'); + $coupon_value = $request->input('coupon_value'); + $coupon_max_uses = $request->input('coupon_uses'); + $coupon_datepicker = $request->input('datepicker'); + $random_codes_amount = $request->input('range_codes'); + $rules = [ + "coupon_type" => "required|string|in:percentage,amount", + "coupon_uses" => "required|integer|digits_between:1,100", + "coupon_value" => "required|numeric|between:0,100", + "datepicker" => "required|date|after:" . Carbon::now()->format(Coupon::formatDate()) + ]; + + // If for some reason you pass both fields at once. + if ($coupon_code && $random_codes_amount) { + return redirect()->back()->with('error', __('Only one of the two code inputs must be provided.'))->withInput($request->all()); + } + + if (!$coupon_code && !$random_codes_amount) { + return redirect()->back()->with('error', __('At least one of the two code inputs must be provided.'))->withInput($request->all()); + } + + if ($coupon_code) { + $rules['coupon_code'] = 'required|string|min:4'; + } elseif ($random_codes_amount) { + $rules['range_codes'] = 'required|integer|digits_between:1,100'; + } + + $request->validate($rules); + + if (array_key_exists('range_codes', $rules)) { + $data = []; + $coupons = Coupon::generateRandomCoupon($random_codes_amount); + + // Scroll through all the randomly generated coupons. + foreach ($coupons as $coupon) { + $data[] = [ + 'code' => $coupon, + 'type' => $coupon_type, + 'value' => $coupon_value, + 'max_uses' => $coupon_max_uses, + 'expires_at' => $coupon_datepicker, + 'created_at' => Carbon::now(), // Does not fill in by itself when using the 'insert' method. + 'updated_at' => Carbon::now() + ]; + } + Coupon::insert($data); + } else { + Coupon::create([ + 'code' => $coupon_code, + 'type' => $coupon_type, + 'value' => $coupon_value, + 'max_uses' => $coupon_max_uses, + 'expires_at' => $coupon_datepicker, + ]); + } + + return redirect()->route('admin.coupons.index')->with('success', __("The coupon's was registered successfully.")); + } + + /** + * Display the specified resource. + * + * @param \App\Models\Coupon $coupon + * @return \Illuminate\Http\Response + */ + public function show(Coupon $coupon) + { + // + } + + /** + * Show the form for editing the specified resource. + * + * @param \App\Models\Coupon $coupon + * @return \Illuminate\Http\Response + */ + public function edit(Coupon $coupon) + { + // + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param \App\Models\Coupon $coupon + * @return \Illuminate\Http\Response + */ + public function update(Request $request, Coupon $coupon) + { + // + } + + /** + * Remove the specified resource from storage. + * + * @param \App\Models\Coupon $coupon + * @return \Illuminate\Http\Response + */ + public function destroy(Coupon $coupon) + { + // + } + + public function redeem(Request $request) + { + return $this->validateCoupon($request); + } +} diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 543cdcf69..7c85d44e3 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -9,6 +9,8 @@ use App\Models\Payment; use App\Models\User; use App\Models\ShopProduct; +use App\Models\Coupon; +use App\Traits\Coupon as CouponTrait; use Exception; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; @@ -20,11 +22,16 @@ use App\Helpers\ExtensionHelper; use App\Settings\GeneralSettings; use App\Settings\LocaleSettings; +use Carbon\Carbon; +use Illuminate\Support\Str; class PaymentController extends Controller { const BUY_PERMISSION = 'user.shop.buy'; const VIEW_PERMISSION = "admin.payments.read"; + + use CouponTrait; + /** * @return Application|Factory|View */ @@ -123,6 +130,8 @@ public function pay(Request $request) { $product = ShopProduct::find($request->product_id); $paymentGateway = $request->payment_method; + $coupon_data = null; + $coupon_code = $request->coupon_code; // on free products, we don't need to use a payment gateway $realPrice = $product->price - ($product->price * PartnerDiscount::getDiscount() / 100); @@ -130,6 +139,22 @@ public function pay(Request $request) return $this->handleFreeProduct($product); } + if ($coupon_code) { + $isValidCoupon = $this->validateCoupon($request); + + if ($isValidCoupon->getStatusCode() == 200) { + $coupon_data = $isValidCoupon; + $discountPrice = $this->calcDiscount($product, $isValidCoupon->getData()); + } + } + + if ($coupon_data) { + return redirect()->route('payment.' . $paymentGateway . 'Pay', [ + 'shopProduct' => $product->id, + 'discountPrice' => $discountPrice + ]); + } + return redirect()->route('payment.' . $paymentGateway . 'Pay', ['shopProduct' => $product->id]); } @@ -141,6 +166,11 @@ public function Cancel(Request $request) return redirect()->route('store.index')->with('info', 'Payment was Canceled'); } + protected function getCouponDiscount(float $productPrice, string $discount) + { + return $productPrice - ($productPrice * $discount / 100); + } + /** * @return JsonResponse|mixed * diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 8a0425016..52cb0bffe 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -34,8 +34,7 @@ public function index(UserSettings $user_settings, DiscordSettings $discord_sett 'force_discord_verification' => $user_settings->force_discord_verification, 'discord_client_id' => $discord_settings->client_id, 'discord_client_secret' => $discord_settings->client_secret, - 'referral_enabled' => $referral_settings->enabled, - 'referral_allowed' => $referral_settings->allowed + 'referral_enabled' => $referral_settings->enabled ]); } diff --git a/app/Models/Coupon.php b/app/Models/Coupon.php new file mode 100644 index 000000000..9b8497515 --- /dev/null +++ b/app/Models/Coupon.php @@ -0,0 +1,162 @@ +<?php + +namespace App\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; +use Carbon\Carbon; + +class Coupon extends Model +{ + use HasFactory; + + protected $fillable = [ + 'code', + 'type', + 'value', + 'uses', + 'max_uses', + 'expires_at' + ]; + + /** + * Returns the date format used by the coupons. + * + * @return string + */ + public static function formatDate(): string + { + return 'Y-MM-DD HH:mm:ss'; + } + + /** + * Returns the current state of the coupon. + * + * @return string + */ + public function getStatus() + { + if ($this->uses >= $this->max_uses) { + return 'USES_LIMIT_REACHED'; + } + + if (! is_null($this->expires_at)) { + $expires_at = Carbon::createFromTimeString($this->expires_at)->timestamp; + + if ($expires_at <= Carbon::now()->timestamp) { + return __('EXPIRED'); + } + } + + return __('VALID'); + } + + /** + * Check if a user has already exceeded the uses of a coupon. + * + * @param Request $request The request being made. + * @param CouponSettings $coupon_settings The instance of the coupon settings. + * + * @return bool + */ + public function isLimitsUsesReached($request, $coupon_settings): bool + { + $coupon_uses = $request->user()->coupons()->where('id', $this->id)->count(); + + return $coupon_uses >= $coupon_settings->max_uses_per_user ? true : false; + } + + /** + * Generate a specified quantity of coupon codes. + * + * @param int $amount Amount of coupons to be generated. + * + * @return array + */ + public static function generateRandomCoupon(int $amount = 10): array + { + $coupons = []; + + for ($i = 0; $i < $amount; $i++) { + $random_coupon = strtoupper(bin2hex(random_bytes(3))); + + $coupons[] = $random_coupon; + } + + return $coupons; + } + + /** + * Standardize queries into one single function. + * + * @param string $code Coupon Code. + * @param array $attributes Attributes to be returned. + * + * @return mixed + */ + protected function getQueryData(string $code, array $attributes): mixed + { + $query = (Coupon::where('code', $code) + ->where('expires_at', '>', Carbon::now()) + ->whereColumn('uses', '<=', 'max_uses') + ->get($attributes)->toArray() + ); + + // When there are results, it comes nested arrays, idk why. This is the solution for now. + $results = count($query) > 0 ? $query[0] : $query; + + if (empty($results)) { + return []; + } + + return $results; + } + + /** + * Get the data from a coupon. + * + * @param string $code Coupon Code. + * @param array $attributes Attributes of a coupon. + * + * @return mixed + */ + public function getCoupon(string $code, array $attributes = ['percentage']): mixed + { + $coupon = $this->getQueryData($code, $attributes); + + if (is_null($coupon)) { + return null; + } + + return $coupon; + } + + /** + * Increments the use of a coupon. + * + * @param string $code Coupon Code. + * @param int $amount Amount to increment. + * + * @return null|bool + */ + public function incrementUses(string $code, int $amount = 1): null|bool + { + $coupon = $this->getQueryData($code, ['uses', 'max_uses']); + + if (empty($coupon) || $coupon['uses'] == $coupon['max_uses']) { + return null; + } + + $this->where('code', $code)->increment('uses', $amount); + + return true; + } + + /** + * @return BelongsToMany + */ + public function users() + { + return $this->belongsToMany(User::class, 'user_coupons'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 2c632e3cd..7c04a65bb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,8 +4,6 @@ use App\Notifications\Auth\QueuedVerifyEmail; use App\Notifications\WelcomeMessage; -use App\Settings\GeneralSettings; -use App\Settings\UserSettings; use App\Classes\PterodactylClient; use App\Settings\PterodactylSettings; use Illuminate\Contracts\Auth\MustVerifyEmail; @@ -170,6 +168,14 @@ public function vouchers() return $this->belongsToMany(Voucher::class); } + /** + * @return BelongsToMany + */ + public function coupons() + { + return $this->belongsToMany(Coupon::class, 'user_coupons'); + } + /** * @return HasOne */ diff --git a/app/Settings/CouponSettings.php b/app/Settings/CouponSettings.php new file mode 100644 index 000000000..45f9c25bf --- /dev/null +++ b/app/Settings/CouponSettings.php @@ -0,0 +1,43 @@ +<?php + +namespace App\Settings; + +use Spatie\LaravelSettings\Settings; + +class CouponSettings extends Settings +{ + public ?int $max_uses_per_user; + + public static function group(): string + { + return 'coupon'; + } + + /** + * Summary of validations array + * @return array<string, string> + */ + public static function getValidations() + { + return [ + 'max_uses_per_user' => 'required|integer' + ]; + } + + /** + * Summary of optionInputData array + * Only used for the settings page + * @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>> + */ + public static function getOptionInputData() + { + return [ + "category_icon" => "fas fa-ticket-alt", + 'max_uses_per_user' => [ + 'label' => 'Max Uses Per User', + 'type' => 'number', + 'description' => 'Maximum number of uses that a user can make of the same coupon.', + ] + ]; + } +} diff --git a/app/Traits/Coupon.php b/app/Traits/Coupon.php new file mode 100644 index 000000000..319fd890e --- /dev/null +++ b/app/Traits/Coupon.php @@ -0,0 +1,86 @@ +<?php + +namespace App\Traits; + +use App\Settings\CouponSettings; +use App\Models\Coupon as CouponModel; +use App\Models\ShopProduct; +use Illuminate\Http\Request; +use Illuminate\Http\JsonResponse; +use stdClass; + +trait Coupon +{ + public function validateCoupon(Request $request): JsonResponse + { + $coupon = CouponModel::where('code', $request->input('coupon_code'))->first(); + $coupon_settings = new CouponSettings; + $response = response()->json([ + 'isValid' => false, + 'error' => __('This coupon does not exist.') + ], 404); + + if (!is_null($coupon)) { + if ($coupon->getStatus() == 'USES_LIMIT_REACHED') { + $response = response()->json([ + 'isValid' => false, + 'error' => __('This coupon has reached the maximum amount of uses.') + ], 422); + + return $response; + } + + if ($coupon->getStatus() == 'EXPIRED') { + $response = response()->json([ + 'isValid' => false, + 'error' => __('This coupon has expired.') + ], 422); + + return $response; + } + + if ($coupon->isLimitsUsesReached($request, $coupon_settings)) { + $response = response()->json([ + 'isValid' => false, + 'error' => __('You have reached the maximum uses of this coupon.') + ], 422); + + return $response; + } + + $response = response()->json([ + 'isValid' => true, + 'couponCode' => $coupon->code, + 'couponType' => $coupon->type, + 'couponValue' => $coupon->value + ]); + } + + return $response; + } + + public function calcDiscount(ShopProduct $product, stdClass $data) + { + + if ($data->isValid) { + $productPrice = $product->price; + + if (is_string($productPrice)) { + $productPrice = floatval($product->price); + } + + if ($data->couponType === 'percentage') { + return $productPrice - ($productPrice * $data->couponValue / 100); + } + + if ($data->couponType === 'amount') { + // There is no discount if the value of the coupon is greater than or equal to the value of the product. + if ($data->couponValue >= $productPrice) { + return $productPrice; + } + } + + return $productPrice - $data->couponValue; + } + } +} diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore old mode 100644 new mode 100755 diff --git a/config/permissions_web.php b/config/permissions_web.php index 1c3828f70..f0918375b 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -74,6 +74,9 @@ 'admin.partners.read', 'admin.partners.write', + 'admin.coupons.read', + 'admin.coupons.write', + 'admin.logs.read', /* diff --git a/config/settings.php b/config/settings.php index 67838a5c8..6b6ae4b72 100644 --- a/config/settings.php +++ b/config/settings.php @@ -12,6 +12,7 @@ use App\Settings\UserSettings; use App\Settings\WebsiteSettings; use App\Settings\TicketSettings; +use App\Settings\CouponSettings; return [ @@ -31,6 +32,7 @@ UserSettings::class, WebsiteSettings::class, TicketSettings::class, + CouponSettings::class, ], /* diff --git a/database/migrations/2023_05_11_153719_create_coupons_table.php b/database/migrations/2023_05_11_153719_create_coupons_table.php new file mode 100644 index 000000000..48048f6ab --- /dev/null +++ b/database/migrations/2023_05_11_153719_create_coupons_table.php @@ -0,0 +1,37 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::create('coupons', function (Blueprint $table) { + $table->id(); + $table->string('code')->unique(); + $table->enum('type', ['percentage', 'amount']); + $table->integer('value'); + $table->integer('uses')->default(0); + $table->integer('max_uses'); + $table->timestamp('expires_at'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('coupons'); + } +}; diff --git a/database/migrations/2023_05_14_152604_create_user_coupons_table.php b/database/migrations/2023_05_14_152604_create_user_coupons_table.php new file mode 100644 index 000000000..10f584aec --- /dev/null +++ b/database/migrations/2023_05_14_152604_create_user_coupons_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::create('user_coupons', function (Blueprint $table) { + $table->foreignId('user_id')->constrained(); + $table->foreignId('coupon_id')->constrained(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_coupons'); + } +}; diff --git a/database/settings/2023_05_12_170041_create_coupon_settings.php b/database/settings/2023_05_12_170041_create_coupon_settings.php new file mode 100644 index 000000000..77505cea8 --- /dev/null +++ b/database/settings/2023_05_12_170041_create_coupon_settings.php @@ -0,0 +1,16 @@ +<?php + +use Spatie\LaravelSettings\Migrations\SettingsMigration; + +return new class extends SettingsMigration +{ + public function up(): void + { + $this->migrator->add('coupon.max_uses_per_user', 1); + } + + public function down(): void + { + $this->migrator->delete('coupon.max_uses_per_user'); + } +}; diff --git a/package-lock.json b/package-lock.json index c687dafdf..89d691a53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "controllpanelgg", + "name": "controlpanel", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/routes/web.php b/routes/web.php index 5e09c048b..bdfd06f5c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -22,6 +22,7 @@ use App\Http\Controllers\Admin\UsefulLinkController; use App\Http\Controllers\Admin\UserController; use App\Http\Controllers\Admin\VoucherController; +use App\Http\Controllers\Admin\CouponController; use App\Http\Controllers\Auth\SocialiteController; use App\Http\Controllers\HomeController; use App\Http\Controllers\NotificationController; @@ -199,6 +200,10 @@ Route::get('partners/{voucher}/users', [PartnerController::class, 'users'])->name('partners.users'); Route::resource('partners', PartnerController::class); + //coupons + Route::post('coupons/redeem', [CouponController::class, 'redeem'])->name('coupon.redeem'); + Route::resource('coupons', CouponController::class); + //api-keys Route::get('api/datatable', [ApplicationApiController::class, 'datatable'])->name('api.datatable'); Route::resource('api', ApplicationApiController::class)->parameters([ diff --git a/storage/app/.gitignore b/storage/app/.gitignore old mode 100644 new mode 100755 diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore old mode 100644 new mode 100755 diff --git a/storage/debugbar/.gitignore b/storage/debugbar/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore old mode 100644 new mode 100755 diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore old mode 100644 new mode 100755 diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore old mode 100644 new mode 100755 diff --git a/themes/default/views/admin/coupons/create.blade.php b/themes/default/views/admin/coupons/create.blade.php new file mode 100644 index 000000000..2b19d6a77 --- /dev/null +++ b/themes/default/views/admin/coupons/create.blade.php @@ -0,0 +1,289 @@ +@extends('layouts.main') + +@section('content') + <!-- CONTENT HEADER --> + <section class="content-header"> + <div class="container-fluid"> + <div class="row mb-2"> + <div class="col-sm-6"> + <h1>{{__('Coupon')}}</h1> + </div> + <div class="col-sm-6"> + <ol class="breadcrumb float-sm-right"> + <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> + <li class="breadcrumb-item"><a href="{{route('admin.coupons.index')}}">{{__('Coupons')}}</a> + </li> + <li class="breadcrumb-item"><a class="text-muted" + href="{{route('admin.coupons.create')}}">{{__('Create')}}</a> + </li> + </ol> + </div> + </div> + </div> + </section> + <!-- END CONTENT HEADER --> + + <!-- MAIN CONTENT --> + <section class="content"> + <div class="container-fluid"> + <div class="row"> + <div class="col-lg-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title"> + <i class="nav-icon fas fa-ticket-alt"></i> + {{__('Coupon Details')}} + </h5> + </div> + <div class="card-body"> + <form action="{{ route('admin.coupons.store') }}" method="POST"> + @csrf + + <div class="d-flex flex-row-reverse"> + <div class="custom-control custom-switch"> + <input + type="checkbox" + id="random_codes" + name="random_codes" + class="custom-control-input" + > + <label for="random_codes" class="custom-control-label"> + {{ __('Random Codes') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('Replace the creation of a single code with several at once with a custom field.')}}" + class="fas fa-info-circle"> + </i> + </label> + </div> + </div> + <div id="range_codes_element" style="display: none;" class="form-group"> + <label for="range_codes"> + {{ __('Range Codes') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('Generate a number of random codes.')}}" + class="fas fa-info-circle"> + </i> + </label> + <input + type="number" + id="range_codes" + name="range_codes" + step="any" + min="1" + max="100" + class="form-control @error('range_codes') is-invalid @enderror" + > + @error('range_codes') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + <div id="coupon_code_element" class="form-group"> + <label for="coupon_code"> + {{ __('Coupon Code') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The coupon code to be registered.')}}" + class="fas fa-info-circle"> + </i> + </label> + <input + type="text" + id="coupon_code" + name="coupon_code" + placeholder="SUMMER" + class="form-control @error('coupon_code') is-invalid @enderror" + value="{{ old('coupon_code') }}" + > + @error('coupon_code') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group"> + <div class="custom-control mb-3 p-0"> + <label for="coupon_type"> + {{ __('Coupon Type') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The way the coupon should discount.')}}" + class="fas fa-info-circle"> + </i> + </label> + <select + name="coupon_type" + id="coupon_type" + class="custom-select @error('coupon_type') is_invalid @enderror" + style="width: 100%; cursor: pointer;" + autocomplete="off" + required + > + <option value="percentage" @if(old('coupon_type') == 'percentage') selected @endif>{{ __('Percentage') }}</option> + <option value="amount" @if(old('coupon_type') == 'amount') selected @endif>{{ __('Amount') }}</option> + </select> + @error('coupon_type') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + </div> + <div class="form-group"> + <div class="input-group d-flex flex-column"> + <label for="coupon_value"> + {{ __('Coupon Value') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The value that the coupon will represent.')}}" + class="fas fa-info-circle"> + </i> + </label> + <div class="d-flex"> + <input + name="coupon_value" + id="coupon_value" + type="number" + step="any" + min="1" + max="100" + class="form-control @error('coupon_value') is-invalid @enderror" + value="{{ old('coupon_value') }}" + > + <span id="input_percentage" class="input-group-text">%</span> + </div> + @error('coupon_value') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + </div> + <div class="form-group"> + <label for="coupon_uses"> + {{ __('Max uses') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The maximum number of times the coupon can be used.')}}" + class="fas fa-info-circle"> + </i> + </label> + <input + name="coupon_uses" + id="coupon_uses" + type="number" + step="any" + min="1" + max="100" + class="form-control @error('coupon_uses') is-invalid @enderror" + value="{{ old('coupon_uses') }}" + > + @error('coupon_uses') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + <div class="d-flex flex-column input-group form-group date" id="datepicker" data-target-input="nearest"> + <label for="datepicker"> + {{ __('Expires at') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The date when the coupon will expire.')}}" + class="fas fa-info-circle"> + </i> + </label> + <div class="d-flex"> + <input + value="{{old('datepicker')}}" + name="datepicker" + placeholder="yyyy-mm-dd hh:mm:ss" + type="text" + class="form-control @error('datepicker') is-invalid @enderror datetimepicker-input" + data-target="#datepicker" + /> + <div + class="input-group-append" + data-target="#datepicker" + data-toggle="datetimepicker" + > + <div class="input-group-text"> + <i class="fa fa-calendar"></i> + </div> + </div> + </div> + @error('datepicker') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group text-right mb-0"> + <button type="submit" class="btn btn-primary"> + {{__('Submit')}} + </button> + </div> + </form> + </div> + </div> + </div> + </div> + </div> + </section> + <!-- END CONTENT --> + + <script> + $(document).ready(function() { + $('#datepicker').datetimepicker({ + format: 'Y-MM-DD HH:mm:ss', + icons: { + time: 'far fa-clock', + date: 'far fa-calendar', + up: 'fas fa-arrow-up', + down: 'fas fa-arrow-down', + previous: 'fas fa-chevron-left', + next: 'fas fa-chevron-right', + today: 'fas fa-calendar-check', + clear: 'far fa-trash-alt', + close: 'far fa-times-circle' + } + }); + $('#random_codes').change(function() { + if ($(this).is(':checked')) { + $('#coupon_code_element').prop('disabled', true).hide() + $('#range_codes_element').prop('disabled', false).show() + + if ($('#coupon_code').val()) { + $('#coupon_code').prop('value', null) + } + + } else { + $('#coupon_code_element').prop('disabled', false).show() + $('#range_codes_element').prop('disabled', true).hide() + + if ($('#range_codes').val()) { + $('#range_codes').prop('value', null) + } + } + }) + + $('#coupon_type').change(function() { + if ($(this).val() == 'percentage') { + $('#input_percentage').prop('disabled', false).show() + } else { + $('#input_percentage').prop('disabled', true).hide() + } + }) + }) + </script> +@endsection diff --git a/themes/default/views/admin/coupons/index.blade.php b/themes/default/views/admin/coupons/index.blade.php new file mode 100644 index 000000000..814b41ab6 --- /dev/null +++ b/themes/default/views/admin/coupons/index.blade.php @@ -0,0 +1,65 @@ +@extends('layouts.main') + +@section('content') + <!-- CONTENT HEADER --> + <section class="content-header"> + <div class="container-fluid"> + <div class="row mb-2"> + <div class="col-sm-6"> + <h1>{{__('Coupons')}}</h1> + </div> + <div class="col-sm-6"> + <ol class="breadcrumb float-sm-right"> + <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> + <li class="breadcrumb-item"><a class="text-muted" + href="{{route('admin.coupons.index')}}">{{__('Coupons')}}</a></li> + </ol> + </div> + </div> + </div> + </section> + <!-- END CONTENT HEADER --> + + <!-- MAIN CONTENT --> + <section class="content"> + <div class="container-fluid"> + <div class="card"> + <div class="card-header"> + <div class="d-flex justify-content-between"> + <h5 class="card-title"> + <i class="nav-icon fas fa-ticket-alt"></i> + {{__('Coupons')}} + </h5> + <a href="{{route('admin.coupons.create')}}" class="btn btn-sm btn-primary"> + <i class="fas fa-plus mr-1"></i> + {{__('Create new')}} + </a> + </div> + </div> + + <div class="card-body table-responsive"> + + <table id="datatable" class="table table-striped"> + <thead> + <tr> + <th>{{__('Partner discount')}}</th> + <th>{{__('Registered user discount')}}</th> + <th>{{__('Referral system commission')}}</th> + <th>{{__('Created')}}</th> + <th>{{__('Actions')}}</th> + </tr> + </thead> + <tbody> + </tbody> + </table> + + </div> + </div> + + + </div> + <!-- END CUSTOM CONTENT --> + +</section> +<!-- END CONTENT --> +@endsection diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 994f2bfc6..9631ef64d 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -424,7 +424,7 @@ class="nav-link @if (Request::routeIs('admin.store.*')) active @endif"> </a> </li> @endcanany - @canany(["admin.voucher.read","admin.voucher.read"]) + @canany(["admin.voucher.read","admin.voucher.write"]) <li class="nav-item"> <a href="{{ route('admin.vouchers.index') }}" class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif"> @@ -433,7 +433,7 @@ class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif"> </a> </li> @endcanany - @canany(["admin.partners.read","admin.partners.read"]) + @canany(["admin.partners.read","admin.partners.write"]) <li class="nav-item"> <a href="{{ route('admin.partners.index') }}" class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif"> @@ -443,6 +443,16 @@ class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif"> </li> @endcanany + @canany(["admin.coupons.read", "admin.coupons.write"]) + <li class="nav-item"> + <a href="{{ route('admin.coupons.index') }}" + class="nav-link @if (Request::routeIs('admin.coupons.*')) active @endif"> + <i class="nav-icon fas fa-ticket-alt"></i> + <p>{{ __('Coupons') }}</p> + </a> + </li> + @endcanany + @canany(["admin.useful_links.read","admin.legal.read"]) <li class="nav-header">{{ __('Other') }}</li> @endcanany diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index a6ca68860..690e6c044 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -24,7 +24,19 @@ <!-- MAIN CONTENT --> <section class="content"> <div class="container-fluid"> - <form x-data="{ payment_method: '', clicked: false }" action="{{ route('payment.pay') }}" method="POST"> + <form + id="payment_form" + action="{{ route('payment.pay') }}" + method="POST" + x-data="{ + payment_method: '', + coupon_code: '', + clicked: false, + setCouponCode(event) { + this.coupon_code = event.target.value + } + }" + > @csrf @method('post') <div class="row d-flex justify-content-center flex-wrap"> @@ -67,6 +79,45 @@ </div> </div> </div> + <div class="col-xl-4"> + <div class="card"> + <div class="card-header"> + <h4 class="mb-0"> + Coupon Code + </h4> + </div> + <div class="card-body"> + <div class="d-flex"> + <input + type="text" + id="coupon_code" + name="coupon_code" + value="{{ old('coupon_code') }}" + :value="coupon_code" + class="form-control @error('coupon_code') is_invalid @enderror" + placeholder="SUMMER" + x-on:change.debounce="setCouponCode($event)" + x-model="coupon_code" + /> + <button + type="button" + id="send_coupon_code" + class="btn btn-success ml-3" + :disabled="!coupon_code.length" + :class="!coupon_code.length ? 'disabled' : ''" + :value="coupon_code" + > + {{ __('Submit') }} + </button> + </div> + @error('coupon_code') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + </div> + </div> @endif <div class="col-xl-3"> <div class="card"> @@ -131,6 +182,12 @@ class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $ <span class="text-muted d-inline-block"> + {{ $product->formatToCurrency($taxvalue) }}</span> </div> + <div id="coupon_discount_details" class="d-flex justify-content-between" style="display: none !important;"> + <span class="text-muted d-inline-block"> + {{ __('Coupon Discount') }} + + </span> + </div> @if ($discountpercent && $discountvalue) <div class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Discount') }} @@ -155,9 +212,12 @@ class="text-muted d-inline-block">{{ $product->formatToCurrency($total) }}</span </li> </ul> - <button :disabled="(!payment_method || !clicked) && {{ !$productIsFree }}" - :class="(!payment_method || !clicked) && {{ !$productIsFree }} ? 'disabled' : ''" + <button :disabled="(!payment_method || !clicked || coupon_code ? true : false) && {{ !$productIsFree }}" + id="submit_form_button" + :class="(!payment_method || !clicked || coupon_code ? true : false) && {{ !$productIsFree }} ? 'disabled' : ''" + :x-text="coupon_code" class="btn btn-success float-right w-100"> + <i class="far fa-credit-card mr-2" @click="clicked == true"></i> @if ($productIsFree) {{ __('Get for free') }} @@ -166,6 +226,8 @@ class="btn btn-success float-right w-100"> @endif </button> + <script> + </script> </div> </div> </div> @@ -175,4 +237,64 @@ class="btn btn-success float-right w-100"> </section> <!-- END CONTENT --> + + <script> + $(document).ready(function() { + let hasCouponCodeValue = $('#coupon_code').val().trim() !== '' + + $('#coupon_code').on('change', function(e) { + hasCouponCodeValue = e.target.value !== '' + }) + + function checkCoupon() { + const couponCode = $('#coupon_code').val() + + $.ajax({ + url: "{{ route('admin.coupon.redeem') }}", + method: 'POST', + data: { coupon_code: couponCode }, + success: function(response) { + if (response.isValid && response.couponCode) { + Swal.fire({ + icon: 'success', + text: `The coupon '${response.couponCode}' was successfully inserted in your purchase.`, + }).then(function(isConfirmed) { + console.log('confirmou') + + $('#submit_form_button').prop('disabled', false).removeClass('disabled') + $('#send_coupon_code').prop('disabled', true) + $('#coupon_discount_details').prop('disabled', false).show() + }) + + } else { + console.log('Invalid Coupon') + } + }, + error: function(response) { + const responseJson = response.responseJSON + + if (!responseJson.isValid) { + Swal.fire({ + icon: 'error', + title: 'Oops...', + text: responseJson.error, + }) + } + } + }) + } + + $('#payment_form').on('submit', function(e) { + if (hasCouponCodeValue) { + checkCoupon() + } + }) + + $('#send_coupon_code').click(function(e) { + if (hasCouponCodeValue) { + checkCoupon() + } + }) + }) + </script> @endsection From 8f7ad95506fedacf7bbba0cff8887bd12e36e66d Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Thu, 18 May 2023 01:13:10 +0000 Subject: [PATCH 190/514] Some improvements, and some not so much... --- .../PayPal/PayPalExtension.php | 49 ++++++++++--------- .../PaymentGateways/PayPal/web_routes.php | 4 +- .../Controllers/Admin/CouponController.php | 2 +- .../Controllers/Admin/PaymentController.php | 17 +------ app/Models/Coupon.php | 6 +-- app/Traits/Coupon.php | 32 +++++++++--- ...2023_05_11_153719_create_coupons_table.php | 4 +- themes/default/views/store/checkout.blade.php | 36 ++++++++++++-- 8 files changed, 92 insertions(+), 58 deletions(-) diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index 73d632df7..1586804b5 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -11,6 +11,7 @@ use App\Models\ShopProduct; use App\Models\User; use App\Models\Coupon; +use App\Traits\Coupon as CouponTrait; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redirect; @@ -28,6 +29,8 @@ */ class PayPalExtension extends AbstractExtension { + use CouponTrait; + public static function getConfig(): array { return [ @@ -36,23 +39,27 @@ public static function getConfig(): array ]; } - static function PaypalPay(Request $request): void + public function PaypalPay(Request $request): void { /** @var User $user */ $user = Auth::user(); $shopProduct = ShopProduct::findOrFail($request->shopProduct); $discount = PartnerDiscount::getDiscount(); - $discountPrice = $request->get('discountPrice'); + $couponCode = $request->input('couponCode'); + $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); - dd($discountPrice); + if (is_string($shopProduct->price)) { + $shopProduct->price = floatval($shopProduct->price); + } - // Partner Discount. - $price = $shopProduct->price - ($shopProduct->price * $discount / 100); + $price = $shopProduct->price; - // Coupon Discount. - // if ($discountPrice) { - // $price = $price - ($price * floatval($coupon_percentage) / 100); - // } + if ($isValidCoupon->getStatusCode() == 200) { + $price = $this->calcDiscount($price, $isValidCoupon->getData()); + } + + // Partner Discount. + $price = $price - ($price * $discount / 100); // create a new payment $payment = Payment::create([ @@ -78,17 +85,15 @@ static function PaypalPay(Request $request): void [ "reference_id" => uniqid(), "description" => $shopProduct->display . ($discount ? (" (" . __('Discount') . " " . $discount . '%)') : ""), - "amount" => [ - "value" => $shopProduct->getTotalPrice(), + "amount" => [ + "value" => $shopProduct->getTotalPrice(), 'currency_code' => strtoupper($shopProduct->currency_code), 'breakdown' => [ - 'item_total' => - [ + 'item_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), 'value' => number_format($price, 2), ], - 'tax_total' => - [ + 'tax_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), 'value' => $shopProduct->getTaxValue(), ] @@ -98,7 +103,7 @@ static function PaypalPay(Request $request): void ], "application_context" => [ "cancel_url" => route('payment.Cancel'), - "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id, 'couponCode' => $coupon_code]), + "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id, 'couponCode' => $couponCode]), 'brand_name' => config('app.name', 'CtrlPanel.GG'), 'shipping_preference' => 'NO_SHIPPING' ] @@ -138,7 +143,7 @@ static function PaypalSuccess(Request $laravelRequest): void $payment = Payment::findOrFail($laravelRequest->payment); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); - $coupon_code = $laravelRequest->input('couponCode'); + $coupon_code = $laravelRequest->input('couponCode'); $request = new OrdersCaptureRequest($laravelRequest->input('token')); $request->prefer('return=representation'); @@ -153,11 +158,11 @@ static function PaypalSuccess(Request $laravelRequest): void 'payment_id' => $response->result->id, ]); - // increase the use of the coupon when the payment is confirmed. - if ($coupon_code) { - $coupon = new Coupon; - $coupon->incrementUses($coupon_code); - } + // increase the use of the coupon when the payment is confirmed. + if ($coupon_code) { + $coupon = new Coupon; + $coupon->incrementUses($coupon_code); + } event(new UserUpdateCreditsEvent($user)); event(new PaymentEvent($user, $payment, $shopProduct)); diff --git a/app/Extensions/PaymentGateways/PayPal/web_routes.php b/app/Extensions/PaymentGateways/PayPal/web_routes.php index 95ef6ef2e..17d1ed028 100644 --- a/app/Extensions/PaymentGateways/PayPal/web_routes.php +++ b/app/Extensions/PaymentGateways/PayPal/web_routes.php @@ -4,8 +4,8 @@ use App\Extensions\PaymentGateways\PayPal\PayPalExtension; Route::middleware(['web', 'auth'])->group(function () { - Route::get('payment/PayPalPay/{shopProduct}', function () { - PayPalExtension::PaypalPay(request()); + Route::get('payment/PayPalPay/{shopProduct}', function (PayPalExtension $payPalExtension) { + $payPalExtension->PaypalPay(request()); })->name('payment.PayPalPay'); Route::get( diff --git a/app/Http/Controllers/Admin/CouponController.php b/app/Http/Controllers/Admin/CouponController.php index 44c3d6181..396e29366 100644 --- a/app/Http/Controllers/Admin/CouponController.php +++ b/app/Http/Controllers/Admin/CouponController.php @@ -154,6 +154,6 @@ public function destroy(Coupon $coupon) public function redeem(Request $request) { - return $this->validateCoupon($request); + return $this->validateCoupon($request->user(), $request->input('couponCode'), $request->input('productId')); } } diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 7c85d44e3..5ab740d9f 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -130,7 +130,6 @@ public function pay(Request $request) { $product = ShopProduct::find($request->product_id); $paymentGateway = $request->payment_method; - $coupon_data = null; $coupon_code = $request->coupon_code; // on free products, we don't need to use a payment gateway @@ -140,18 +139,9 @@ public function pay(Request $request) } if ($coupon_code) { - $isValidCoupon = $this->validateCoupon($request); - - if ($isValidCoupon->getStatusCode() == 200) { - $coupon_data = $isValidCoupon; - $discountPrice = $this->calcDiscount($product, $isValidCoupon->getData()); - } - } - - if ($coupon_data) { return redirect()->route('payment.' . $paymentGateway . 'Pay', [ 'shopProduct' => $product->id, - 'discountPrice' => $discountPrice + 'couponCode' => $coupon_code ]); } @@ -166,11 +156,6 @@ public function Cancel(Request $request) return redirect()->route('store.index')->with('info', 'Payment was Canceled'); } - protected function getCouponDiscount(float $productPrice, string $discount) - { - return $productPrice - ($productPrice * $discount / 100); - } - /** * @return JsonResponse|mixed * diff --git a/app/Models/Coupon.php b/app/Models/Coupon.php index 9b8497515..d7f0efb38 100644 --- a/app/Models/Coupon.php +++ b/app/Models/Coupon.php @@ -40,7 +40,7 @@ public function getStatus() return 'USES_LIMIT_REACHED'; } - if (! is_null($this->expires_at)) { + if (!is_null($this->expires_at)) { $expires_at = Carbon::createFromTimeString($this->expires_at)->timestamp; if ($expires_at <= Carbon::now()->timestamp) { @@ -59,9 +59,9 @@ public function getStatus() * * @return bool */ - public function isLimitsUsesReached($request, $coupon_settings): bool + public function isLimitsUsesReached($requestUser, $coupon_settings): bool { - $coupon_uses = $request->user()->coupons()->where('id', $this->id)->count(); + $coupon_uses = $requestUser->coupons()->where('id', $this->id)->count(); return $coupon_uses >= $coupon_settings->max_uses_per_user ? true : false; } diff --git a/app/Traits/Coupon.php b/app/Traits/Coupon.php index 319fd890e..be4da4ad8 100644 --- a/app/Traits/Coupon.php +++ b/app/Traits/Coupon.php @@ -11,9 +11,10 @@ trait Coupon { - public function validateCoupon(Request $request): JsonResponse + public function validateCoupon($requestUser, $couponCode, $productId): JsonResponse { - $coupon = CouponModel::where('code', $request->input('coupon_code'))->first(); + $coupon = CouponModel::where('code', $couponCode)->first(); + $shopProduct = ShopProduct::findOrFail($productId); $coupon_settings = new CouponSettings; $response = response()->json([ 'isValid' => false, @@ -21,6 +22,14 @@ public function validateCoupon(Request $request): JsonResponse ], 404); if (!is_null($coupon)) { + if (is_string($coupon->value)) { + $coupon->value = floatval($coupon->value); + } + + if (is_string($shopProduct->price)) { + $shopProduct->price = floatval($shopProduct->price); + } + if ($coupon->getStatus() == 'USES_LIMIT_REACHED') { $response = response()->json([ 'isValid' => false, @@ -39,7 +48,7 @@ public function validateCoupon(Request $request): JsonResponse return $response; } - if ($coupon->isLimitsUsesReached($request, $coupon_settings)) { + if ($coupon->isLimitsUsesReached($requestUser, $coupon_settings)) { $response = response()->json([ 'isValid' => false, 'error' => __('You have reached the maximum uses of this coupon.') @@ -48,6 +57,15 @@ public function validateCoupon(Request $request): JsonResponse return $response; } + if ($coupon->type === 'amount' && $coupon->value >= $shopProduct->price) { + $response = response()->json([ + 'isValid' => false, + 'error' => __('The coupon you are trying to use would give you 100% off, so it cannot be used for this product, sorry.') + ], 422); + + return $response; + } + $response = response()->json([ 'isValid' => true, 'couponCode' => $coupon->code, @@ -59,14 +77,12 @@ public function validateCoupon(Request $request): JsonResponse return $response; } - public function calcDiscount(ShopProduct $product, stdClass $data) + public function calcDiscount($productPrice, stdClass $data) { if ($data->isValid) { - $productPrice = $product->price; - if (is_string($productPrice)) { - $productPrice = floatval($product->price); + $productPrice = floatval($productPrice); } if ($data->couponType === 'percentage') { @@ -82,5 +98,7 @@ public function calcDiscount(ShopProduct $product, stdClass $data) return $productPrice - $data->couponValue; } + + return $productPrice; } } diff --git a/database/migrations/2023_05_11_153719_create_coupons_table.php b/database/migrations/2023_05_11_153719_create_coupons_table.php index 48048f6ab..db26a65b5 100644 --- a/database/migrations/2023_05_11_153719_create_coupons_table.php +++ b/database/migrations/2023_05_11_153719_create_coupons_table.php @@ -17,10 +17,10 @@ public function up() $table->id(); $table->string('code')->unique(); $table->enum('type', ['percentage', 'amount']); - $table->integer('value'); + $table->decimal('value', 10); $table->integer('uses')->default(0); $table->integer('max_uses'); - $table->timestamp('expires_at'); + $table->timestamp('expires_at')->nullable(); $table->timestamps(); }); } diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index 690e6c044..59ea53c18 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -185,6 +185,8 @@ class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $ <div id="coupon_discount_details" class="d-flex justify-content-between" style="display: none !important;"> <span class="text-muted d-inline-block"> {{ __('Coupon Discount') }} + </span> + <span id="coupon_discount_value" class="text-muted d-inline-block"> </span> </div> @@ -199,8 +201,13 @@ class="text-muted d-inline-block">-{{ $product->formatToCurrency($discountvalue) <hr class="text-white border-secondary"> <div class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Total') }}</span> + <input id="total_price_input" type="hidden" value="{{ $product->getTotalPrice() }}"> <span - class="text-muted d-inline-block">{{ $product->formatToCurrency($total) }}</span> + id="total_price" + class="text-muted d-inline-block" + > + {{ $product->formatToCurrency($total) }} + </span> </div> <template x-if="payment_method"> <div class="d-flex justify-content-between"> @@ -240,27 +247,46 @@ class="btn btn-success float-right w-100"> <script> $(document).ready(function() { + const productId = $("[name='product_id']").val() let hasCouponCodeValue = $('#coupon_code').val().trim() !== '' $('#coupon_code').on('change', function(e) { hasCouponCodeValue = e.target.value !== '' }) + function calcPriceWithCouponDiscount(couponValue, couponType) { + let totalPrice = $('#total_price_input').val() + + if (typeof totalPrice == 'string') { + totalPrice = parseFloat(totalPrice) + } + + if (couponType === 'percentage') { + totalPrice = totalPrice - (totalPrice * couponValue / 100) + $('#coupon_discount_value').text("- " + couponValue + "%") + } else if (couponType === 'amount') { + totalPrice = totalPrice - couponValue + $('#coupon_discount_value').text(totalPrice) + } + + $('#total_price').text(totalPrice) + $('#total_price_input').val(totalPrice) + } + function checkCoupon() { const couponCode = $('#coupon_code').val() $.ajax({ url: "{{ route('admin.coupon.redeem') }}", method: 'POST', - data: { coupon_code: couponCode }, + data: { couponCode: couponCode, productId: productId }, success: function(response) { if (response.isValid && response.couponCode) { Swal.fire({ icon: 'success', - text: `The coupon '${response.couponCode}' was successfully inserted in your purchase.`, + text: 'The coupon was successfully added to your purchase.', }).then(function(isConfirmed) { - console.log('confirmou') - + calcPriceWithCouponDiscount(response.couponValue, response.couponType) $('#submit_form_button').prop('disabled', false).removeClass('disabled') $('#send_coupon_code').prop('disabled', true) $('#coupon_discount_details').prop('disabled', false).show() From 490e11572d73b1d5daf1e133356a33e29cd20aec Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Thu, 18 May 2023 14:33:42 +0000 Subject: [PATCH 191/514] Use casts instead of converting values --- .../PaymentGateways/PayPal/PayPalExtension.php | 6 +----- app/Models/Coupon.php | 17 ++++++++++++++--- app/Models/ShopProduct.php | 7 +++++++ app/Traits/Coupon.php | 12 ------------ 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index 1586804b5..7667c08d9 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -47,13 +47,9 @@ public function PaypalPay(Request $request): void $discount = PartnerDiscount::getDiscount(); $couponCode = $request->input('couponCode'); $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); - - if (is_string($shopProduct->price)) { - $shopProduct->price = floatval($shopProduct->price); - } - $price = $shopProduct->price; + // Coupon Discount. if ($isValidCoupon->getStatusCode() == 200) { $price = $this->calcDiscount($price, $isValidCoupon->getData()); } diff --git a/app/Models/Coupon.php b/app/Models/Coupon.php index d7f0efb38..20657db5b 100644 --- a/app/Models/Coupon.php +++ b/app/Models/Coupon.php @@ -10,6 +10,9 @@ class Coupon extends Model { use HasFactory; + /** + * @var string[] + */ protected $fillable = [ 'code', 'type', @@ -19,6 +22,16 @@ class Coupon extends Model 'expires_at' ]; + /** + * @var string[] + */ + protected $casts = [ + 'value' => 'float', + 'uses' => 'integer', + 'max_uses' => 'integer', + 'expires_at' => 'timestamp' + ]; + /** * Returns the date format used by the coupons. * @@ -41,9 +54,7 @@ public function getStatus() } if (!is_null($this->expires_at)) { - $expires_at = Carbon::createFromTimeString($this->expires_at)->timestamp; - - if ($expires_at <= Carbon::now()->timestamp) { + if ($this->expires_at <= Carbon::now()->timestamp) { return __('EXPIRED'); } } diff --git a/app/Models/ShopProduct.php b/app/Models/ShopProduct.php index cfe3f7838..12b870a28 100644 --- a/app/Models/ShopProduct.php +++ b/app/Models/ShopProduct.php @@ -36,6 +36,13 @@ public function getActivitylogOptions(): LogOptions 'disabled', ]; + /** + * @var string[] + */ + protected $casts = [ + 'price' => 'float' + ]; + public static function boot() { parent::boot(); diff --git a/app/Traits/Coupon.php b/app/Traits/Coupon.php index be4da4ad8..091fa855b 100644 --- a/app/Traits/Coupon.php +++ b/app/Traits/Coupon.php @@ -22,14 +22,6 @@ public function validateCoupon($requestUser, $couponCode, $productId): JsonRespo ], 404); if (!is_null($coupon)) { - if (is_string($coupon->value)) { - $coupon->value = floatval($coupon->value); - } - - if (is_string($shopProduct->price)) { - $shopProduct->price = floatval($shopProduct->price); - } - if ($coupon->getStatus() == 'USES_LIMIT_REACHED') { $response = response()->json([ 'isValid' => false, @@ -81,10 +73,6 @@ public function calcDiscount($productPrice, stdClass $data) { if ($data->isValid) { - if (is_string($productPrice)) { - $productPrice = floatval($productPrice); - } - if ($data->couponType === 'percentage') { return $productPrice - ($productPrice * $data->couponValue / 100); } From 640468acbee4cb5724545645c828089d7c022508 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Thu, 18 May 2023 19:51:10 +0000 Subject: [PATCH 192/514] Finish the coupon base. --- app/Console/Commands/DeleteExpiredCoupons.php | 42 +++ app/Console/Kernel.php | 2 + app/Events/CouponUsedEvent.php | 25 ++ .../PayPal/PayPalExtension.php | 8 +- .../Controllers/Admin/CouponController.php | 149 ++++++--- app/Listeners/CouponUsed.php | 47 +++ app/Providers/EventServiceProvider.php | 5 + app/Settings/CouponSettings.php | 18 +- ...23_05_12_170041_create_coupon_settings.php | 4 + routes/web.php | 1 + .../views/admin/coupons/create.blade.php | 76 ++--- .../views/admin/coupons/edit.blade.php | 291 ++++++++++++++++++ .../views/admin/coupons/index.blade.php | 39 ++- 13 files changed, 626 insertions(+), 81 deletions(-) create mode 100644 app/Console/Commands/DeleteExpiredCoupons.php create mode 100644 app/Events/CouponUsedEvent.php create mode 100644 app/Listeners/CouponUsed.php create mode 100644 themes/default/views/admin/coupons/edit.blade.php diff --git a/app/Console/Commands/DeleteExpiredCoupons.php b/app/Console/Commands/DeleteExpiredCoupons.php new file mode 100644 index 000000000..9d3ded87a --- /dev/null +++ b/app/Console/Commands/DeleteExpiredCoupons.php @@ -0,0 +1,42 @@ +<?php + +namespace App\Console\Commands; + +use App\Settings\CouponSettings; +use App\Models\Coupon; +use Carbon\Carbon; +use Illuminate\Console\Command; + + +class DeleteExpiredCoupons extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'coupons:delete'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Delete expired coupons from DB.'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(CouponSettings $couponSettings) + { + if ($couponSettings->delete_coupon_on_expires) { + $expired_coupons = Coupon::where('expires_at', '<=', Carbon::now(config('app.timezone')))->get(); + + foreach ($expired_coupons as $expired_coupon) { + $expired_coupon->delete(); + } + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 64af565f9..a1bd37d22 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -16,6 +16,7 @@ class Kernel extends ConsoleKernel protected $commands = [ Commands\ChargeCreditsCommand::class, Commands\ChargeServers::class, + Commands\DeleteExpiredCoupons::class, ]; /** @@ -29,6 +30,7 @@ protected function schedule(Schedule $schedule) $schedule->command('servers:charge')->everyMinute(); $schedule->command('cp:versioncheck:get')->daily(); $schedule->command('payments:open:clear')->daily(); + $schedule->command('coupons:delete')->daily(); //log cronjob activity $schedule->call(function () { diff --git a/app/Events/CouponUsedEvent.php b/app/Events/CouponUsedEvent.php new file mode 100644 index 000000000..49168b602 --- /dev/null +++ b/app/Events/CouponUsedEvent.php @@ -0,0 +1,25 @@ +<?php + +namespace App\Events; + +use App\Models\Coupon; +use Illuminate\Broadcasting\InteractsWithSockets; +use Illuminate\Foundation\Events\Dispatchable; +use Illuminate\Queue\SerializesModels; + +class CouponUsedEvent +{ + use Dispatchable, InteractsWithSockets, SerializesModels; + + public Coupon $coupon; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct(Coupon $coupon) + { + $this->coupon = $coupon; + } +} diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index 7667c08d9..07771e486 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -2,6 +2,7 @@ namespace App\Extensions\PaymentGateways\PayPal; +use App\Events\CouponUsedEvent; use App\Helpers\AbstractExtension; use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; @@ -56,6 +57,7 @@ public function PaypalPay(Request $request): void // Partner Discount. $price = $price - ($price * $discount / 100); + $price = number_format($price, 2); // create a new payment $payment = Payment::create([ @@ -82,12 +84,12 @@ public function PaypalPay(Request $request): void "reference_id" => uniqid(), "description" => $shopProduct->display . ($discount ? (" (" . __('Discount') . " " . $discount . '%)') : ""), "amount" => [ - "value" => $shopProduct->getTotalPrice(), + "value" => $price, 'currency_code' => strtoupper($shopProduct->currency_code), 'breakdown' => [ 'item_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), - 'value' => number_format($price, 2), + 'value' => $price ], 'tax_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), @@ -158,6 +160,8 @@ static function PaypalSuccess(Request $laravelRequest): void if ($coupon_code) { $coupon = new Coupon; $coupon->incrementUses($coupon_code); + + event(new CouponUsedEvent($coupon)); } event(new UserUpdateCreditsEvent($user)); diff --git a/app/Http/Controllers/Admin/CouponController.php b/app/Http/Controllers/Admin/CouponController.php index 396e29366..1287a57c7 100644 --- a/app/Http/Controllers/Admin/CouponController.php +++ b/app/Http/Controllers/Admin/CouponController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Models\Coupon; +use App\Settings\LocaleSettings; use App\Traits\Coupon as CouponTrait; use Illuminate\Http\Request; use Carbon\Carbon; @@ -20,11 +21,13 @@ class CouponController extends Controller * * @return \Illuminate\Http\Response */ - public function index() + public function index(LocaleSettings $localeSettings) { $this->checkPermission(self::READ_PERMISSION); - return view('admin.coupons.index'); + return view('admin.coupons.index', [ + 'locale_datatables' => $localeSettings->datatables + ]); } /** @@ -47,21 +50,12 @@ public function create() */ public function store(Request $request) { - $coupon_code = $request->input('coupon_code'); - $coupon_type = $request->input('coupon_type'); - $coupon_value = $request->input('coupon_value'); - $coupon_max_uses = $request->input('coupon_uses'); - $coupon_datepicker = $request->input('datepicker'); + $coupon_code = $request->input('code'); $random_codes_amount = $request->input('range_codes'); - $rules = [ - "coupon_type" => "required|string|in:percentage,amount", - "coupon_uses" => "required|integer|digits_between:1,100", - "coupon_value" => "required|numeric|between:0,100", - "datepicker" => "required|date|after:" . Carbon::now()->format(Coupon::formatDate()) - ]; + $rules = $this->requestRules($request); - // If for some reason you pass both fields at once. - if ($coupon_code && $random_codes_amount) { + // If for some reason you pass both fields at once. + if ($coupon_code && $random_codes_amount) { return redirect()->back()->with('error', __('Only one of the two code inputs must be provided.'))->withInput($request->all()); } @@ -69,12 +63,6 @@ public function store(Request $request) return redirect()->back()->with('error', __('At least one of the two code inputs must be provided.'))->withInput($request->all()); } - if ($coupon_code) { - $rules['coupon_code'] = 'required|string|min:4'; - } elseif ($random_codes_amount) { - $rules['range_codes'] = 'required|integer|digits_between:1,100'; - } - $request->validate($rules); if (array_key_exists('range_codes', $rules)) { @@ -85,23 +73,17 @@ public function store(Request $request) foreach ($coupons as $coupon) { $data[] = [ 'code' => $coupon, - 'type' => $coupon_type, - 'value' => $coupon_value, - 'max_uses' => $coupon_max_uses, - 'expires_at' => $coupon_datepicker, + 'type' => $request->input('type'), + 'value' => $request->input('value'), + 'max_uses' => $request->input('max_uses'), + 'expires_at' => $request->input('expires_at'), 'created_at' => Carbon::now(), // Does not fill in by itself when using the 'insert' method. 'updated_at' => Carbon::now() ]; } Coupon::insert($data); } else { - Coupon::create([ - 'code' => $coupon_code, - 'type' => $coupon_type, - 'value' => $coupon_value, - 'max_uses' => $coupon_max_uses, - 'expires_at' => $coupon_datepicker, - ]); + Coupon::create($request->except('_token')); } return redirect()->route('admin.coupons.index')->with('success', __("The coupon's was registered successfully.")); @@ -126,7 +108,12 @@ public function show(Coupon $coupon) */ public function edit(Coupon $coupon) { - // + $this->checkPermission(self::WRITE_PERMISSION); + + return view('admin.coupons.edit', [ + 'coupon' => $coupon, + 'expired_at' => $coupon->expires_at ? Carbon::createFromTimestamp($coupon->expires_at) : null + ]); } /** @@ -138,7 +125,23 @@ public function edit(Coupon $coupon) */ public function update(Request $request, Coupon $coupon) { - // + $coupon_code = $request->input('code'); + $random_codes_amount = $request->input('range_codes'); + $rules = $this->requestRules($request); + + // If for some reason you pass both fields at once. + if ($coupon_code && $random_codes_amount) { + return redirect()->back()->with('error', __('Only one of the two code inputs must be provided.'))->withInput($request->all()); + } + + if (!$coupon_code && !$random_codes_amount) { + return redirect()->back()->with('error', __('At least one of the two code inputs must be provided.'))->withInput($request->all()); + } + + $request->validate($rules); + $coupon->update($request->except('_token')); + + return redirect()->route('admin.coupons.index')->with('success', __('coupon has been updated!')); } /** @@ -149,11 +152,87 @@ public function update(Request $request, Coupon $coupon) */ public function destroy(Coupon $coupon) { - // + $this->checkPermission(self::WRITE_PERMISSION); + $coupon->delete(); + + return redirect()->back()->with('success', __('coupon has been removed!')); + } + + private function requestRules(Request $request) + { + $coupon_code = $request->input('code'); + $random_codes_amount = $request->input('range_codes'); + $rules = [ + "type" => "required|string|in:percentage,amount", + "max_uses" => "required|integer|digits_between:1,100", + "value" => "required|numeric|between:0,100", + "expires_at" => "nullable|date|after:" . Carbon::now()->format(Coupon::formatDate()) + ]; + + if ($coupon_code) { + $rules['code'] = "required|string|min:4"; + } elseif ($random_codes_amount) { + $rules['range_codes'] = 'required|integer|digits_between:1,100'; + } + + return $rules; } public function redeem(Request $request) { return $this->validateCoupon($request->user(), $request->input('couponCode'), $request->input('productId')); } + + public function dataTable() + { + $query = Coupon::query(); + + return datatables($query) + ->addColumn('actions', function(Coupon $coupon) { + return ' + <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.coupons.edit', $coupon->id).'" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> + + <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('admin.coupons.destroy', $coupon->id).'"> + '.csrf_field().' + '.method_field('DELETE').' + <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + </form> + '; + }) + ->addColumn('status', function(Coupon $coupon) { + $color = 'success'; + $status = $coupon->getStatus(); + + if ($status != __('VALID')) { + $color = 'danger'; + } + + return '<span class="badge badge-'.$color.'">'.str_replace('_', ' ', $status).'</span>'; + }) + ->editColumn('uses', function (Coupon $coupon) { + return "{$coupon->uses} / {$coupon->max_uses}"; + }) + ->editColumn('value', function (Coupon $coupon) { + if ($coupon->type === 'percentage') { + return $coupon->value . "%"; + } + + return number_format($coupon->value, 2, '.', ''); + }) + ->editColumn('expires_at', function (Coupon $coupon) { + if (!$coupon->expires_at) { + return __('Never'); + } + + return Carbon::createFromTimestamp($coupon->expires_at); + }) + ->editColumn('created_at', function(Coupon $coupon) { + return Carbon::createFromTimeString($coupon->created_at); + }) + ->editColumn('code', function (Coupon $coupon) { + return "<code>{$coupon->code}</code>"; + }) + ->rawColumns(['actions', 'code', 'status']) + ->make(); + } } diff --git a/app/Listeners/CouponUsed.php b/app/Listeners/CouponUsed.php new file mode 100644 index 000000000..75ced4eb4 --- /dev/null +++ b/app/Listeners/CouponUsed.php @@ -0,0 +1,47 @@ +<?php + +namespace App\Listeners; + +use App\Events\CouponUsedEvent; +use App\Settings\CouponSettings; +use Carbon\Carbon; + +class CouponUsed +{ + private $delete_coupon_on_expires; + private $delete_coupon_on_uses_reached; + + /** + * Create the event listener. + * + * @return void + */ + public function __construct(CouponSettings $couponSettings) + { + $this->delete_coupon_on_expires = $couponSettings->delete_coupon_on_expires; + $this->delete_coupon_on_uses_reached = $couponSettings->delete_coupon_on_uses_reached; + } + + /** + * Handle the event. + * + * @param \App\Events\CouponUsedEvent $event + * @return void + */ + public function handle(CouponUsedEvent $event) + { + if ($this->delete_coupon_on_expires) { + if (!is_null($event->coupon->expired_at)) { + if ($event->coupon->expires_at <= Carbon::now()->timestamp) { + $event->coupon->delete(); + } + } + } + + if ($this->delete_coupon_on_uses_reached) { + if ($event->coupon->uses >= $event->coupon->max_uses) { + $event->coupon->delete(); + } + } + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index bdd71a3ae..2ac9182dd 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -4,6 +4,8 @@ use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; +use App\Events\CouponUsedEvent; +use App\Listeners\CouponUsed; use App\Listeners\CreateInvoice; use App\Listeners\UnsuspendServers; use App\Listeners\UserPayment; @@ -31,6 +33,9 @@ class EventServiceProvider extends ServiceProvider CreateInvoice::class, UserPayment::class, ], + CouponUsedEvent::class => [ + CouponUsed::class + ], SocialiteWasCalled::class => [ // ... other providers 'SocialiteProviders\\Discord\\DiscordExtendSocialite@handle', diff --git a/app/Settings/CouponSettings.php b/app/Settings/CouponSettings.php index 45f9c25bf..29ec33320 100644 --- a/app/Settings/CouponSettings.php +++ b/app/Settings/CouponSettings.php @@ -7,6 +7,8 @@ class CouponSettings extends Settings { public ?int $max_uses_per_user; + public ?bool $delete_coupon_on_expires; + public ?bool $delete_coupon_on_uses_reached; public static function group(): string { @@ -20,7 +22,9 @@ public static function group(): string public static function getValidations() { return [ - 'max_uses_per_user' => 'required|integer' + 'max_uses_per_user' => 'required|integer', + 'delete_coupon_on_expires' => 'required|boolean', + 'delete_coupon_on_uses_reached' => 'required|boolean', ]; } @@ -36,7 +40,17 @@ public static function getOptionInputData() 'max_uses_per_user' => [ 'label' => 'Max Uses Per User', 'type' => 'number', - 'description' => 'Maximum number of uses that a user can make of the same coupon.', + 'description' => 'Maximum number of uses that a user can make of the same coupon.' + ], + 'delete_coupon_on_expires' => [ + 'label' => 'Delete Coupon On Expires', + 'type' => 'boolean', + 'description' => 'Automatically deletes the coupon if it expires.' + ], + 'delete_coupon_on_uses_reached' => [ + 'label' => 'Delete Coupon When Max Uses Reached', + 'type' => 'boolean', + 'description' => 'Delete a coupon as soon as its maximum usage is reached.' ] ]; } diff --git a/database/settings/2023_05_12_170041_create_coupon_settings.php b/database/settings/2023_05_12_170041_create_coupon_settings.php index 77505cea8..9ff792ce5 100644 --- a/database/settings/2023_05_12_170041_create_coupon_settings.php +++ b/database/settings/2023_05_12_170041_create_coupon_settings.php @@ -7,10 +7,14 @@ public function up(): void { $this->migrator->add('coupon.max_uses_per_user', 1); + $this->migrator->add('coupon.delete_coupon_on_expires', false); + $this->migrator->add('coupon.delete_coupon_on_uses_reached', false); } public function down(): void { $this->migrator->delete('coupon.max_uses_per_user'); + $this->migrator->delete('coupon.delete_coupon_on_expires'); + $this->migrator->delete('coupon.delete_coupon_on_uses_reached'); } }; diff --git a/routes/web.php b/routes/web.php index bdfd06f5c..a6a2ab3cc 100644 --- a/routes/web.php +++ b/routes/web.php @@ -201,6 +201,7 @@ Route::resource('partners', PartnerController::class); //coupons + Route::get('coupons/datatable', [CouponController::class, 'dataTable'])->name('coupons.datatable'); Route::post('coupons/redeem', [CouponController::class, 'redeem'])->name('coupon.redeem'); Route::resource('coupons', CouponController::class); diff --git a/themes/default/views/admin/coupons/create.blade.php b/themes/default/views/admin/coupons/create.blade.php index 2b19d6a77..7cef744e7 100644 --- a/themes/default/views/admin/coupons/create.blade.php +++ b/themes/default/views/admin/coupons/create.blade.php @@ -84,7 +84,7 @@ class="form-control @error('range_codes') is-invalid @enderror" @enderror </div> <div id="coupon_code_element" class="form-group"> - <label for="coupon_code"> + <label for="code"> {{ __('Coupon Code') }} <i data-toggle="popover" @@ -95,13 +95,13 @@ class="fas fa-info-circle"> </label> <input type="text" - id="coupon_code" - name="coupon_code" + id="code" + name="code" placeholder="SUMMER" - class="form-control @error('coupon_code') is-invalid @enderror" - value="{{ old('coupon_code') }}" + class="form-control @error('code') is-invalid @enderror" + value="{{ old('code') }}" > - @error('coupon_code') + @error('code') <div class="text-danger"> {{ $message }} </div> @@ -109,7 +109,7 @@ class="form-control @error('coupon_code') is-invalid @enderror" </div> <div class="form-group"> <div class="custom-control mb-3 p-0"> - <label for="coupon_type"> + <label for="type"> {{ __('Coupon Type') }} <i data-toggle="popover" @@ -119,17 +119,17 @@ class="fas fa-info-circle"> </i> </label> <select - name="coupon_type" - id="coupon_type" - class="custom-select @error('coupon_type') is_invalid @enderror" + name="type" + id="type" + class="custom-select @error('type') is_invalid @enderror" style="width: 100%; cursor: pointer;" autocomplete="off" required > - <option value="percentage" @if(old('coupon_type') == 'percentage') selected @endif>{{ __('Percentage') }}</option> - <option value="amount" @if(old('coupon_type') == 'amount') selected @endif>{{ __('Amount') }}</option> + <option value="percentage" @if(old('type') == 'percentage') selected @endif>{{ __('Percentage') }}</option> + <option value="amount" @if(old('type') == 'amount') selected @endif>{{ __('Amount') }}</option> </select> - @error('coupon_type') + @error('type') <div class="text-danger"> {{ $message }} </div> @@ -138,7 +138,7 @@ class="custom-select @error('coupon_type') is_invalid @enderror" </div> <div class="form-group"> <div class="input-group d-flex flex-column"> - <label for="coupon_value"> + <label for="value"> {{ __('Coupon Value') }} <i data-toggle="popover" @@ -149,18 +149,18 @@ class="fas fa-info-circle"> </label> <div class="d-flex"> <input - name="coupon_value" - id="coupon_value" + name="value" + id="value" type="number" step="any" min="1" max="100" - class="form-control @error('coupon_value') is-invalid @enderror" - value="{{ old('coupon_value') }}" + class="form-control @error('value') is-invalid @enderror" + value="{{ old('value') }}" > <span id="input_percentage" class="input-group-text">%</span> </div> - @error('coupon_value') + @error('value') <div class="text-danger"> {{ $message }} </div> @@ -168,7 +168,7 @@ class="form-control @error('coupon_value') is-invalid @enderror" </div> </div> <div class="form-group"> - <label for="coupon_uses"> + <label for="max_uses"> {{ __('Max uses') }} <i data-toggle="popover" @@ -178,43 +178,43 @@ class="fas fa-info-circle"> </i> </label> <input - name="coupon_uses" - id="coupon_uses" + name="max_uses" + id="max_uses" type="number" step="any" min="1" max="100" - class="form-control @error('coupon_uses') is-invalid @enderror" - value="{{ old('coupon_uses') }}" + class="form-control @error('max_uses') is-invalid @enderror" + value="{{ old('max_uses') }}" > - @error('coupon_uses') + @error('max_uses') <div class="text-danger"> {{ $message }} </div> @enderror </div> - <div class="d-flex flex-column input-group form-group date" id="datepicker" data-target-input="nearest"> - <label for="datepicker"> + <div class="d-flex flex-column input-group form-group date" id="expires_at" data-target-input="nearest"> + <label for="expires_at"> {{ __('Expires at') }} <i data-toggle="popover" data-trigger="hover" - data-content="{{__('The date when the coupon will expire.')}}" + data-content="{{__('The date when the coupon will expire (If no date is provided, the coupon never expires).')}}" class="fas fa-info-circle"> </i> </label> <div class="d-flex"> <input - value="{{old('datepicker')}}" - name="datepicker" + value="{{ old('expires_at') }}" + name="expires_at" placeholder="yyyy-mm-dd hh:mm:ss" type="text" - class="form-control @error('datepicker') is-invalid @enderror datetimepicker-input" - data-target="#datepicker" + class="form-control @error('expires_at') is-invalid @enderror datetimepicker-input" + data-target="#expires_at" /> <div class="input-group-append" - data-target="#datepicker" + data-target="#expires_at" data-toggle="datetimepicker" > <div class="input-group-text"> @@ -222,7 +222,7 @@ class="input-group-append" </div> </div> </div> - @error('datepicker') + @error('expires_at') <div class="text-danger"> {{ $message }} </div> @@ -244,7 +244,7 @@ class="input-group-append" <script> $(document).ready(function() { - $('#datepicker').datetimepicker({ + $('#expires_at').datetimepicker({ format: 'Y-MM-DD HH:mm:ss', icons: { time: 'far fa-clock', @@ -263,8 +263,8 @@ class="input-group-append" $('#coupon_code_element').prop('disabled', true).hide() $('#range_codes_element').prop('disabled', false).show() - if ($('#coupon_code').val()) { - $('#coupon_code').prop('value', null) + if ($('#code').val()) { + $('#code').prop('value', null) } } else { @@ -277,7 +277,7 @@ class="input-group-append" } }) - $('#coupon_type').change(function() { + $('#type').change(function() { if ($(this).val() == 'percentage') { $('#input_percentage').prop('disabled', false).show() } else { diff --git a/themes/default/views/admin/coupons/edit.blade.php b/themes/default/views/admin/coupons/edit.blade.php new file mode 100644 index 000000000..aa3c81eec --- /dev/null +++ b/themes/default/views/admin/coupons/edit.blade.php @@ -0,0 +1,291 @@ +@extends('layouts.main') + +@section('content') + <!-- CONTENT HEADER --> + <section class="content-header"> + <div class="container-fluid"> + <div class="row mb-2"> + <div class="col-sm-6"> + <h1>{{__('Coupon')}}</h1> + </div> + <div class="col-sm-6"> + <ol class="breadcrumb float-sm-right"> + <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> + <li class="breadcrumb-item"><a href="{{route('admin.coupons.index')}}">{{__('Coupon')}}</a> + </li> + <li class="breadcrumb-item"><a class="text-muted" + href="{{route('admin.coupons.edit' , $coupon->id)}}">{{__('Edit')}}</a> + </li> + </ol> + </div> + </div> + </div> + </section> + <!-- END CONTENT HEADER --> + + <!-- MAIN CONTENT --> + <section class="content"> + <div class="container-fluid"> + + <div class="row"> + <div class="col-lg-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title"> + <i class="fas fa-money-check-alt mr-2"></i>{{__('Coupon details')}} + </h5> + </div> + <div class="card-body"> + <form action="{{ route('admin.coupons.update', $coupon->id) }}" method="POST"> + @csrf + @method('PATCH') + + <div class="d-flex flex-row-reverse"> + <div class="custom-control custom-switch"> + <input + type="checkbox" + id="random_codes" + name="random_codes" + class="custom-control-input" + > + <label for="random_codes" class="custom-control-label"> + {{ __('Random Codes') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('Replace the creation of a single code with several at once with a custom field.')}}" + class="fas fa-info-circle"> + </i> + </label> + </div> + </div> + <div id="range_codes_element" style="display: none;" class="form-group"> + <label for="range_codes"> + {{ __('Range Codes') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('Generate a number of random codes.')}}" + class="fas fa-info-circle"> + </i> + </label> + <input + type="number" + id="range_codes" + name="range_codes" + step="any" + min="1" + max="100" + class="form-control @error('range_codes') is-invalid @enderror" + > + @error('range_codes') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + <div id="coupon_code_element" class="form-group"> + <label for="code"> + {{ __('Coupon Code') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The coupon code to be registered.')}}" + class="fas fa-info-circle"> + </i> + </label> + <input + type="text" + id="code" + name="code" + placeholder="SUMMER" + class="form-control @error('code') is-invalid @enderror" + value="{{ $coupon->code }}" + > + @error('code') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group"> + <div class="custom-control mb-3 p-0"> + <label for="type"> + {{ __('Coupon Type') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The way the coupon should discount.')}}" + class="fas fa-info-circle"> + </i> + </label> + <select + name="type" + id="type" + class="custom-select @error('type') is_invalid @enderror" + style="width: 100%; cursor: pointer;" + autocomplete="off" + required + > + <option value="percentage" @if($coupon->type == 'percentage') selected @endif>{{ __('Percentage') }}</option> + <option value="amount" @if($coupon->type == 'amount') selected @endif>{{ __('Amount') }}</option> + </select> + @error('type') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + </div> + <div class="form-group"> + <div class="input-group d-flex flex-column"> + <label for="value"> + {{ __('Coupon Value') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The value that the coupon will represent.')}}" + class="fas fa-info-circle"> + </i> + </label> + <div class="d-flex"> + <input + name="value" + id="value" + type="number" + step="any" + min="1" + max="100" + class="form-control @error('value') is-invalid @enderror" + value="{{ $coupon->value }}" + > + <span id="input_percentage" class="input-group-text">%</span> + </div> + @error('value') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + </div> + <div class="form-group"> + <label for="max_uses"> + {{ __('Max uses') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The maximum number of times the coupon can be used.')}}" + class="fas fa-info-circle"> + </i> + </label> + <input + name="max_uses" + id="max_uses" + type="number" + step="any" + min="1" + max="100" + class="form-control @error('max_uses') is-invalid @enderror" + value="{{ $coupon->max_uses }}" + > + @error('max_uses') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + <div class="d-flex flex-column input-group form-group date" id="expires_at" data-target-input="nearest"> + <label for="expires_at"> + {{ __('Expires at') }} + <i + data-toggle="popover" + data-trigger="hover" + data-content="{{__('The date when the coupon will expire (If no date is provided, the coupon never expires).')}}" + class="fas fa-info-circle"> + </i> + </label> + <div class="d-flex"> + <input + value="{{ $expired_at ?? '' }}" + name="expires_at" + placeholder="yyyy-mm-dd hh:mm:ss" + type="text" + class="form-control @error('expires_at') is-invalid @enderror datetimepicker-input" + data-target="#expires_at" + /> + <div + class="input-group-append" + data-target="#expires_at" + data-toggle="datetimepicker" + > + <div class="input-group-text"> + <i class="fa fa-calendar"></i> + </div> + </div> + </div> + @error('expires_at') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + <div class="form-group text-right mb-0"> + <button type="submit" class="btn btn-primary"> + {{__('Submit')}} + </button> + </div> + </form> + </div> + </div> + </div> + </div> + + </div> + </section> + <!-- END CONTENT --> + + <script> + $(document).ready(function() { + $('#expires_at').datetimepicker({ + format: 'Y-MM-DD HH:mm:ss', + icons: { + time: 'far fa-clock', + date: 'far fa-calendar', + up: 'fas fa-arrow-up', + down: 'fas fa-arrow-down', + previous: 'fas fa-chevron-left', + next: 'fas fa-chevron-right', + today: 'fas fa-calendar-check', + clear: 'far fa-trash-alt', + close: 'far fa-times-circle' + } + }); + $('#random_codes').change(function() { + if ($(this).is(':checked')) { + $('#coupon_code_element').prop('disabled', true).hide() + $('#range_codes_element').prop('disabled', false).show() + + if ($('#code').val()) { + $('#code').prop('value', null) + } + + } else { + $('#coupon_code_element').prop('disabled', false).show() + $('#range_codes_element').prop('disabled', true).hide() + + if ($('#range_codes').val()) { + $('#range_codes').prop('value', null) + } + } + }) + + $('#type').change(function() { + if ($(this).val() == 'percentage') { + $('#input_percentage').prop('disabled', false).show() + } else { + $('#input_percentage').prop('disabled', true).hide() + } + }) + }) + </script> +@endsection diff --git a/themes/default/views/admin/coupons/index.blade.php b/themes/default/views/admin/coupons/index.blade.php index 814b41ab6..d89135cff 100644 --- a/themes/default/views/admin/coupons/index.blade.php +++ b/themes/default/views/admin/coupons/index.blade.php @@ -42,10 +42,12 @@ <table id="datatable" class="table table-striped"> <thead> <tr> - <th>{{__('Partner discount')}}</th> - <th>{{__('Registered user discount')}}</th> - <th>{{__('Referral system commission')}}</th> - <th>{{__('Created')}}</th> + <th>{{__('Status')}}</th> + <th>{{__('Code')}}</th> + <th>{{__('Value')}}</th> + <th>{{__('Used / Max Uses')}}</th> + <th>{{__('Expires')}}</th> + <th>{{__('Created At')}}</th> <th>{{__('Actions')}}</th> </tr> </thead> @@ -62,4 +64,33 @@ </section> <!-- END CONTENT --> +<script> + function submitResult() { + return confirm("{{__('Are you sure you wish to delete?')}}") !== false; + } + + $(document).ready(function() { + $('#datatable').DataTable({ + language: { + url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{ $locale_datatables }}.json' + }, + processing: true, + serverSide: true, + stateSave: true, + ajax: "{{route('admin.coupons.datatable')}}", + columns: [ + {data: 'status'}, + {data: 'code'}, + {data: 'value'}, + {data: 'uses'}, + {data: 'expires_at'}, + {data: 'created_at'}, + {data: 'actions', sortable: false}, + ], + fnDrawCallback: function( oSettings ) { + $('[data-toggle="popover"]').popover(); + } + }); + }) +</script> @endsection From bad9d0ec1238858e4bda0c6934661ee93e593b70 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Thu, 18 May 2023 19:52:26 +0000 Subject: [PATCH 193/514] It is good to set the application's timezone. --- app/Models/Coupon.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Coupon.php b/app/Models/Coupon.php index 20657db5b..01133f4a2 100644 --- a/app/Models/Coupon.php +++ b/app/Models/Coupon.php @@ -54,7 +54,7 @@ public function getStatus() } if (!is_null($this->expires_at)) { - if ($this->expires_at <= Carbon::now()->timestamp) { + if ($this->expires_at <= Carbon::now(config('app.timezone'))->timestamp) { return __('EXPIRED'); } } From 24ce267962ef57d71354dbeaea8ac732789095c6 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Fri, 19 May 2023 14:01:10 +0000 Subject: [PATCH 194/514] Implement coupons for the other gateways. --- .../Mollie/MollieExtension.php | 35 ++++++++-- .../PaymentGateways/Mollie/web_routes.php | 4 +- .../Stripe/StripeExtension.php | 36 ++++++++-- .../PaymentGateways/Stripe/web_routes.php | 4 +- app/Models/Coupon.php | 67 ++++--------------- app/Settings/CouponSettings.php | 2 +- 6 files changed, 78 insertions(+), 70 deletions(-) diff --git a/app/Extensions/PaymentGateways/Mollie/MollieExtension.php b/app/Extensions/PaymentGateways/Mollie/MollieExtension.php index 11cf3c6e1..9145b067d 100644 --- a/app/Extensions/PaymentGateways/Mollie/MollieExtension.php +++ b/app/Extensions/PaymentGateways/Mollie/MollieExtension.php @@ -9,10 +9,12 @@ use App\Models\Payment; use App\Models\ShopProduct; use App\Models\User; +use App\Models\Coupon; +use App\Traits\Coupon as CouponTrait; +use App\Events\CouponUsedEvent; use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Log; @@ -23,6 +25,8 @@ */ class MollieExtension extends AbstractExtension { + use CouponTrait; + public static function getConfig(): array { return [ @@ -33,7 +37,7 @@ public static function getConfig(): array ]; } - static function pay(Request $request): void + public function pay(Request $request): void { $url = 'https://api.mollie.com/v2/payments'; $settings = new MollieSettings(); @@ -41,6 +45,18 @@ static function pay(Request $request): void $user = Auth::user(); $shopProduct = ShopProduct::findOrFail($request->shopProduct); $discount = PartnerDiscount::getDiscount(); + $couponCode = $request->input('couponCode'); + $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); + $price = $shopProduct->price; + + // Coupon Discount. + if ($isValidCoupon->getStatusCode() == 200) { + $price = $this->calcDiscount($price, $isValidCoupon->getData()); + } + + // Partner Discount. + $price = $price - ($price * $discount / 100); + $price = number_format($price, 2, thousands_separator: ''); // create a new payment $payment = Payment::create([ @@ -50,7 +66,7 @@ static function pay(Request $request): void 'type' => $shopProduct->type, 'status' => 'open', 'amount' => $shopProduct->quantity, - 'price' => $shopProduct->price - ($shopProduct->price * $discount / 100), + 'price' => $price, 'tax_value' => $shopProduct->getTaxValue(), 'tax_percent' => $shopProduct->getTaxPercent(), 'total_price' => $shopProduct->getTotalPrice(), @@ -65,10 +81,10 @@ static function pay(Request $request): void ])->post($url, [ 'amount' => [ 'currency' => $shopProduct->currency_code, - 'value' => number_format($shopProduct->getTotalPrice(), 2, '.', ''), + 'value' => $price, ], 'description' => "Order #{$payment->id} - " . $shopProduct->name, - 'redirectUrl' => route('payment.MollieSuccess'), + 'redirectUrl' => route('payment.MollieSuccess', ['couponCode' => $couponCode]), 'cancelUrl' => route('payment.Cancel'), 'webhookUrl' => url('/extensions/payment/MollieWebhook'), 'metadata' => [ @@ -103,6 +119,15 @@ static function success(Request $request): void { $payment = Payment::findOrFail($request->input('payment')); $payment->status = 'pending'; + $coupon_code = $request->input('couponCode'); + + // increase the use of the coupon when the payment is confirmed. + if ($coupon_code) { + $coupon = new Coupon; + $coupon->incrementUses($coupon_code); + + event(new CouponUsedEvent($coupon)); + } Redirect::route('home')->with('success', 'Your payment is being processed')->send(); return; diff --git a/app/Extensions/PaymentGateways/Mollie/web_routes.php b/app/Extensions/PaymentGateways/Mollie/web_routes.php index f5640b9a3..2314774af 100644 --- a/app/Extensions/PaymentGateways/Mollie/web_routes.php +++ b/app/Extensions/PaymentGateways/Mollie/web_routes.php @@ -4,8 +4,8 @@ use App\Extensions\PaymentGateways\Mollie\MollieExtension; Route::middleware(['web', 'auth'])->group(function () { - Route::get('payment/MolliePay/{shopProduct}', function () { - MollieExtension::pay(request()); + Route::get('payment/MolliePay/{shopProduct}', function (MollieExtension $mollieExtension) { + $mollieExtension->pay(request()); })->name('payment.MolliePay'); Route::get( diff --git a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php index 7102e223d..f54157d45 100644 --- a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php +++ b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php @@ -4,12 +4,15 @@ use App\Helpers\AbstractExtension; use App\Events\PaymentEvent; +use App\Events\CouponUsedEvent; use App\Events\UserUpdateCreditsEvent; use App\Extensions\PaymentGateways\Stripe\StripeSettings; use App\Models\PartnerDiscount; use App\Models\Payment; use App\Models\ShopProduct; use App\Models\User; +use App\Models\Coupon; +use App\Traits\Coupon as CouponTrait; use App\Notifications\ConfirmPaymentNotification; use Exception; use Illuminate\Http\Request; @@ -21,6 +24,8 @@ class StripeExtension extends AbstractExtension { + use CouponTrait; + public static function getConfig(): array { return [ @@ -35,10 +40,14 @@ public static function getConfig(): array * @param Request $request * @param ShopProduct $shopProduct */ - public static function StripePay(Request $request) + public function StripePay(Request $request) { $user = Auth::user(); $shopProduct = ShopProduct::findOrFail($request->shopProduct); + $discount = PartnerDiscount::getDiscount(); + $couponCode = $request->input('couponCode'); + $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); + $price = $shopProduct->price; // check if the price is valid for stripe if (!self::checkPriceAmount($shopProduct->getTotalPrice(), strtoupper($shopProduct->currency_code), 'stripe')) { @@ -46,7 +55,14 @@ public static function StripePay(Request $request) return; } - $discount = PartnerDiscount::getDiscount(); + // Coupon Discount. + if ($isValidCoupon->getStatusCode() == 200) { + $price = $this->calcDiscount($price, $isValidCoupon->getData()); + } + + // Partner Discount. + $price = $price - ($price * $discount / 100); + $price = number_format($price, 2); // create payment @@ -57,7 +73,7 @@ public static function StripePay(Request $request) 'type' => $shopProduct->type, 'status' => 'open', 'amount' => $shopProduct->quantity, - 'price' => $shopProduct->price - ($shopProduct->price * $discount / 100), + 'price' => $price, 'tax_value' => $shopProduct->getTaxValue(), 'total_price' => $shopProduct->getTotalPrice(), 'tax_percent' => $shopProduct->getTaxPercent(), @@ -75,7 +91,7 @@ public static function StripePay(Request $request) 'name' => $shopProduct->display . ($discount ? (' (' . __('Discount') . ' ' . $discount . '%)') : ''), 'description' => $shopProduct->description, ], - 'unit_amount_decimal' => round($shopProduct->getPriceAfterDiscount() * 100, 2), + 'unit_amount_decimal' => $price, ], 'quantity' => 1, ], @@ -93,7 +109,7 @@ public static function StripePay(Request $request) ], 'mode' => 'payment', - 'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id]) . '&session_id={CHECKOUT_SESSION_ID}', + 'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id, 'couponCode' => $couponCode]) . '&session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => route('payment.Cancel'), 'payment_intent_data' => [ 'metadata' => [ @@ -114,7 +130,7 @@ public static function StripeSuccess(Request $request) $user = User::findOrFail($user->id); $payment = Payment::findOrFail($request->input('payment')); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); - + $couponCode = $request->input('couponCode'); Redirect::route('home')->with('success', 'Please wait for success')->send(); @@ -136,6 +152,14 @@ public static function StripeSuccess(Request $request) 'status' => 'paid', ]); + // increase the use of the coupon when the payment is confirmed. + if ($couponCode) { + $coupon = new Coupon; + $coupon->incrementUses($couponCode); + + event(new CouponUsedEvent($coupon)); + } + //payment notification $user->notify(new ConfirmPaymentNotification($payment)); diff --git a/app/Extensions/PaymentGateways/Stripe/web_routes.php b/app/Extensions/PaymentGateways/Stripe/web_routes.php index 0f4fed969..c5640aba5 100644 --- a/app/Extensions/PaymentGateways/Stripe/web_routes.php +++ b/app/Extensions/PaymentGateways/Stripe/web_routes.php @@ -4,8 +4,8 @@ use App\Extensions\PaymentGateways\Stripe\StripeExtension; Route::middleware(['web', 'auth'])->group(function () { - Route::get('payment/StripePay/{shopProduct}', function () { - StripeExtension::StripePay(request()); + Route::get('payment/StripePay/{shopProduct}', function (StripeExtension $stripeExtension) { + $stripeExtension->StripePay(request()); })->name('payment.StripePay'); Route::get( diff --git a/app/Models/Coupon.php b/app/Models/Coupon.php index 01133f4a2..97a5b9733 100644 --- a/app/Models/Coupon.php +++ b/app/Models/Coupon.php @@ -4,11 +4,21 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Spatie\Activitylog\LogOptions; +use Spatie\Activitylog\Traits\LogsActivity; use Carbon\Carbon; class Coupon extends Model { - use HasFactory; + use HasFactory, LogsActivity; + + public function getActivitylogOptions(): LogOptions + { + return LogOptions::defaults() + ->logOnlyDirty() + ->logOnly(['*']) + ->dontSubmitEmptyLogs(); + } /** * @var string[] @@ -97,67 +107,16 @@ public static function generateRandomCoupon(int $amount = 10): array return $coupons; } - /** - * Standardize queries into one single function. - * - * @param string $code Coupon Code. - * @param array $attributes Attributes to be returned. - * - * @return mixed - */ - protected function getQueryData(string $code, array $attributes): mixed - { - $query = (Coupon::where('code', $code) - ->where('expires_at', '>', Carbon::now()) - ->whereColumn('uses', '<=', 'max_uses') - ->get($attributes)->toArray() - ); - - // When there are results, it comes nested arrays, idk why. This is the solution for now. - $results = count($query) > 0 ? $query[0] : $query; - - if (empty($results)) { - return []; - } - - return $results; - } - - /** - * Get the data from a coupon. - * - * @param string $code Coupon Code. - * @param array $attributes Attributes of a coupon. - * - * @return mixed - */ - public function getCoupon(string $code, array $attributes = ['percentage']): mixed - { - $coupon = $this->getQueryData($code, $attributes); - - if (is_null($coupon)) { - return null; - } - - return $coupon; - } - /** * Increments the use of a coupon. * * @param string $code Coupon Code. * @param int $amount Amount to increment. * - * @return null|bool + * @return bool */ - public function incrementUses(string $code, int $amount = 1): null|bool + public function incrementUses(string $code, int $amount = 1): bool { - $coupon = $this->getQueryData($code, ['uses', 'max_uses']); - - if (empty($coupon) || $coupon['uses'] == $coupon['max_uses']) { - return null; - } - $this->where('code', $code)->increment('uses', $amount); return true; diff --git a/app/Settings/CouponSettings.php b/app/Settings/CouponSettings.php index 29ec33320..dde8f42ac 100644 --- a/app/Settings/CouponSettings.php +++ b/app/Settings/CouponSettings.php @@ -43,7 +43,7 @@ public static function getOptionInputData() 'description' => 'Maximum number of uses that a user can make of the same coupon.' ], 'delete_coupon_on_expires' => [ - 'label' => 'Delete Coupon On Expires', + 'label' => 'Auto Delete Expired Coupons', 'type' => 'boolean', 'description' => 'Automatically deletes the coupon if it expires.' ], From 4320fa9ef636db4a8184ec92aea54a9d0c343d9d Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Fri, 19 May 2023 15:00:30 +0000 Subject: [PATCH 195/514] Make it possible to disable coupons --- .../Controllers/Admin/PaymentController.php | 9 +-- app/Settings/CouponSettings.php | 29 ++++--- ...23_05_12_170041_create_coupon_settings.php | 2 + themes/default/views/store/checkout.blade.php | 76 ++++++++++--------- 4 files changed, 63 insertions(+), 53 deletions(-) diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 5ab740d9f..1b0d875aa 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -9,7 +9,6 @@ use App\Models\Payment; use App\Models\User; use App\Models\ShopProduct; -use App\Models\Coupon; use App\Traits\Coupon as CouponTrait; use Exception; use Illuminate\Contracts\Foundation\Application; @@ -20,10 +19,9 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use App\Helpers\ExtensionHelper; +use App\Settings\CouponSettings; use App\Settings\GeneralSettings; use App\Settings\LocaleSettings; -use Carbon\Carbon; -use Illuminate\Support\Str; class PaymentController extends Controller { @@ -51,7 +49,7 @@ public function index(LocaleSettings $locale_settings) * @param ShopProduct $shopProduct * @return Application|Factory|View */ - public function checkOut(ShopProduct $shopProduct, GeneralSettings $general_settings) + public function checkOut(ShopProduct $shopProduct, GeneralSettings $general_settings, CouponSettings $coupon_settings) { $this->checkPermission(self::BUY_PERMISSION); @@ -87,7 +85,8 @@ public function checkOut(ShopProduct $shopProduct, GeneralSettings $general_sett 'total' => $shopProduct->getTotalPrice(), 'paymentGateways' => $paymentGateways, 'productIsFree' => $price <= 0, - 'credits_display_name' => $general_settings->credits_display_name + 'credits_display_name' => $general_settings->credits_display_name, + 'isCouponsEnabled' => $coupon_settings->enabled, ]); } diff --git a/app/Settings/CouponSettings.php b/app/Settings/CouponSettings.php index dde8f42ac..7669b06a3 100644 --- a/app/Settings/CouponSettings.php +++ b/app/Settings/CouponSettings.php @@ -6,9 +6,10 @@ class CouponSettings extends Settings { - public ?int $max_uses_per_user; - public ?bool $delete_coupon_on_expires; - public ?bool $delete_coupon_on_uses_reached; + public bool $enabled; + public bool $delete_coupon_on_expires; + public bool $delete_coupon_on_uses_reached; + public ?int $max_uses_per_user; public static function group(): string { @@ -22,9 +23,10 @@ public static function group(): string public static function getValidations() { return [ - 'max_uses_per_user' => 'required|integer', - 'delete_coupon_on_expires' => 'required|boolean', - 'delete_coupon_on_uses_reached' => 'required|boolean', + 'enabled' => "nullable|boolean", + 'delete_coupon_on_expires' => 'nullable|boolean', + 'delete_coupon_on_uses_reached' => 'nullable|boolean', + 'max_uses_per_user' => 'nullable|integer', ]; } @@ -37,10 +39,10 @@ public static function getOptionInputData() { return [ "category_icon" => "fas fa-ticket-alt", - 'max_uses_per_user' => [ - 'label' => 'Max Uses Per User', - 'type' => 'number', - 'description' => 'Maximum number of uses that a user can make of the same coupon.' + 'enabled' => [ + 'label' => 'Enable Coupons', + 'type' => 'boolean', + 'description' => 'Enables coupons to be used in the store.' ], 'delete_coupon_on_expires' => [ 'label' => 'Auto Delete Expired Coupons', @@ -51,7 +53,12 @@ public static function getOptionInputData() 'label' => 'Delete Coupon When Max Uses Reached', 'type' => 'boolean', 'description' => 'Delete a coupon as soon as its maximum usage is reached.' - ] + ], + 'max_uses_per_user' => [ + 'label' => 'Max Uses Per User', + 'type' => 'number', + 'description' => 'Maximum number of uses that a user can make of the same coupon.' + ], ]; } } diff --git a/database/settings/2023_05_12_170041_create_coupon_settings.php b/database/settings/2023_05_12_170041_create_coupon_settings.php index 9ff792ce5..8e912bef3 100644 --- a/database/settings/2023_05_12_170041_create_coupon_settings.php +++ b/database/settings/2023_05_12_170041_create_coupon_settings.php @@ -6,6 +6,7 @@ { public function up(): void { + $this->migrator->add('coupon.enabled', true); $this->migrator->add('coupon.max_uses_per_user', 1); $this->migrator->add('coupon.delete_coupon_on_expires', false); $this->migrator->add('coupon.delete_coupon_on_uses_reached', false); @@ -13,6 +14,7 @@ public function up(): void public function down(): void { + $this->migrator->delete('coupon.enabled'); $this->migrator->delete('coupon.max_uses_per_user'); $this->migrator->delete('coupon.delete_coupon_on_expires'); $this->migrator->delete('coupon.delete_coupon_on_uses_reached'); diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index 59ea53c18..5fc360040 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -79,45 +79,47 @@ </div> </div> </div> - <div class="col-xl-4"> - <div class="card"> - <div class="card-header"> - <h4 class="mb-0"> - Coupon Code - </h4> - </div> - <div class="card-body"> - <div class="d-flex"> - <input - type="text" - id="coupon_code" - name="coupon_code" - value="{{ old('coupon_code') }}" - :value="coupon_code" - class="form-control @error('coupon_code') is_invalid @enderror" - placeholder="SUMMER" - x-on:change.debounce="setCouponCode($event)" - x-model="coupon_code" - /> - <button - type="button" - id="send_coupon_code" - class="btn btn-success ml-3" - :disabled="!coupon_code.length" - :class="!coupon_code.length ? 'disabled' : ''" - :value="coupon_code" - > - {{ __('Submit') }} - </button> + @if ($isCouponsEnabled) + <div class="col-xl-4"> + <div class="card"> + <div class="card-header"> + <h4 class="mb-0"> + Coupon Code + </h4> </div> - @error('coupon_code') - <div class="text-danger"> - {{ $message }} + <div class="card-body"> + <div class="d-flex"> + <input + type="text" + id="coupon_code" + name="coupon_code" + value="{{ old('coupon_code') }}" + :value="coupon_code" + class="form-control @error('coupon_code') is_invalid @enderror" + placeholder="SUMMER" + x-on:change.debounce="setCouponCode($event)" + x-model="coupon_code" + /> + <button + type="button" + id="send_coupon_code" + class="btn btn-success ml-3" + :disabled="!coupon_code.length" + :class="!coupon_code.length ? 'disabled' : ''" + :value="coupon_code" + > + {{ __('Submit') }} + </button> </div> - @enderror - </div> - </div> - </div> + @error('coupon_code') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + </div> + </div> + </div> + @endif @endif <div class="col-xl-3"> <div class="card"> From 6d50834f9c6706bf5feab33b7f0b6eb5a0e64aac Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sat, 20 May 2023 19:55:19 +0000 Subject: [PATCH 196/514] Just by improving the code. --- app/Events/CouponUsedEvent.php | 4 ++- .../Mollie/MollieExtension.php | 10 ++----- .../PayPal/PayPalExtension.php | 10 ++----- .../Stripe/StripeExtension.php | 15 ++++------ app/Listeners/CouponUsed.php | 30 +++++++++++++++++++ app/Models/Coupon.php | 15 ---------- 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/app/Events/CouponUsedEvent.php b/app/Events/CouponUsedEvent.php index 49168b602..ceb0ac376 100644 --- a/app/Events/CouponUsedEvent.php +++ b/app/Events/CouponUsedEvent.php @@ -12,14 +12,16 @@ class CouponUsedEvent use Dispatchable, InteractsWithSockets, SerializesModels; public Coupon $coupon; + public string $couponCode; /** * Create a new event instance. * * @return void */ - public function __construct(Coupon $coupon) + public function __construct(Coupon $coupon, string $couponCode) { $this->coupon = $coupon; + $this->couponCode = $couponCode; } } diff --git a/app/Extensions/PaymentGateways/Mollie/MollieExtension.php b/app/Extensions/PaymentGateways/Mollie/MollieExtension.php index 9145b067d..6edd34112 100644 --- a/app/Extensions/PaymentGateways/Mollie/MollieExtension.php +++ b/app/Extensions/PaymentGateways/Mollie/MollieExtension.php @@ -119,14 +119,10 @@ static function success(Request $request): void { $payment = Payment::findOrFail($request->input('payment')); $payment->status = 'pending'; - $coupon_code = $request->input('couponCode'); - - // increase the use of the coupon when the payment is confirmed. - if ($coupon_code) { - $coupon = new Coupon; - $coupon->incrementUses($coupon_code); + $couponCode = $request->input('couponCode'); - event(new CouponUsedEvent($coupon)); + if ($couponCode) { + event(new CouponUsedEvent(new Coupon, $couponCode)); } Redirect::route('home')->with('success', 'Your payment is being processed')->send(); diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index 07771e486..e62cbe54c 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -141,7 +141,7 @@ static function PaypalSuccess(Request $laravelRequest): void $payment = Payment::findOrFail($laravelRequest->payment); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); - $coupon_code = $laravelRequest->input('couponCode'); + $couponCode = $laravelRequest->input('couponCode'); $request = new OrdersCaptureRequest($laravelRequest->input('token')); $request->prefer('return=representation'); @@ -156,12 +156,8 @@ static function PaypalSuccess(Request $laravelRequest): void 'payment_id' => $response->result->id, ]); - // increase the use of the coupon when the payment is confirmed. - if ($coupon_code) { - $coupon = new Coupon; - $coupon->incrementUses($coupon_code); - - event(new CouponUsedEvent($coupon)); + if ($couponCode) { + event(new CouponUsedEvent(new Coupon, $couponCode)); } event(new UserUpdateCreditsEvent($user)); diff --git a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php index f54157d45..b66655932 100644 --- a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php +++ b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php @@ -44,9 +44,6 @@ public function StripePay(Request $request) { $user = Auth::user(); $shopProduct = ShopProduct::findOrFail($request->shopProduct); - $discount = PartnerDiscount::getDiscount(); - $couponCode = $request->input('couponCode'); - $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); $price = $shopProduct->price; // check if the price is valid for stripe @@ -55,6 +52,10 @@ public function StripePay(Request $request) return; } + $discount = PartnerDiscount::getDiscount(); + $couponCode = $request->input('couponCode'); + $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); + // Coupon Discount. if ($isValidCoupon->getStatusCode() == 200) { $price = $this->calcDiscount($price, $isValidCoupon->getData()); @@ -152,12 +153,8 @@ public static function StripeSuccess(Request $request) 'status' => 'paid', ]); - // increase the use of the coupon when the payment is confirmed. - if ($couponCode) { - $coupon = new Coupon; - $coupon->incrementUses($couponCode); - - event(new CouponUsedEvent($coupon)); + if ($couponCode) { + event(new CouponUsedEvent(new Coupon, $couponCode)); } //payment notification diff --git a/app/Listeners/CouponUsed.php b/app/Listeners/CouponUsed.php index 75ced4eb4..7ba8c34b4 100644 --- a/app/Listeners/CouponUsed.php +++ b/app/Listeners/CouponUsed.php @@ -30,6 +30,14 @@ public function __construct(CouponSettings $couponSettings) */ public function handle(CouponUsedEvent $event) { + // Always check the authenticity of the coupon. + if (!$this->isValidCoupon($event)) { + return; + } + + // Automatically increments the coupon usage. + $this->incrementUses($event); + if ($this->delete_coupon_on_expires) { if (!is_null($event->coupon->expired_at)) { if ($event->coupon->expires_at <= Carbon::now()->timestamp) { @@ -44,4 +52,26 @@ public function handle(CouponUsedEvent $event) } } } + + /** + * Increments the use of a coupon. + * + * @param \App\Events\CouponUsedEvent $event + */ + private function incrementUses(CouponUsedEvent $event) + { + $event->coupon->where('code', $event->coupon->code)->increment('uses'); + } + + /** + * It checks that the coupon received from the request really exists. + * + * @param \App\Events\CouponUsedEvent $event + * + * @return bool + */ + private function isValidCoupon(CouponUsedEvent $event): bool + { + return $event->coupon->code === $event->couponCode ? true : false; + } } diff --git a/app/Models/Coupon.php b/app/Models/Coupon.php index 97a5b9733..082e423fe 100644 --- a/app/Models/Coupon.php +++ b/app/Models/Coupon.php @@ -107,21 +107,6 @@ public static function generateRandomCoupon(int $amount = 10): array return $coupons; } - /** - * Increments the use of a coupon. - * - * @param string $code Coupon Code. - * @param int $amount Amount to increment. - * - * @return bool - */ - public function incrementUses(string $code, int $amount = 1): bool - { - $this->where('code', $code)->increment('uses', $amount); - - return true; - } - /** * @return BelongsToMany */ From c05b4d88202a3110acbea57d95190425e6f21ae8 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 26 May 2023 16:30:23 +0200 Subject: [PATCH 197/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Move=20Payment=20co?= =?UTF-8?q?ncerns=20from=20extension=20to=20payment=20controller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Classes/AbstractExtension.php | 8 +++ app/Classes/PaymentExtensions.php | 14 ++++ .../PayPal/PayPalExtension.php | 69 +++++-------------- app/Helpers/AbstractExtension.php | 9 --- .../Controllers/Admin/PaymentController.php | 68 ++++++++++++++---- 5 files changed, 93 insertions(+), 75 deletions(-) create mode 100644 app/Classes/AbstractExtension.php create mode 100644 app/Classes/PaymentExtensions.php delete mode 100644 app/Helpers/AbstractExtension.php diff --git a/app/Classes/AbstractExtension.php b/app/Classes/AbstractExtension.php new file mode 100644 index 000000000..7c0683980 --- /dev/null +++ b/app/Classes/AbstractExtension.php @@ -0,0 +1,8 @@ +<?php + +namespace App\Classes; + +abstract class AbstractExtension +{ + abstract public static function getConfig(): array; +} diff --git a/app/Classes/PaymentExtensions.php b/app/Classes/PaymentExtensions.php new file mode 100644 index 000000000..30bb1e19f --- /dev/null +++ b/app/Classes/PaymentExtensions.php @@ -0,0 +1,14 @@ +<?php + +namespace App\Classes; + +use App\Models\Payment; +use App\Models\ShopProduct; + +abstract class PaymentExtension extends AbstractExtension +{ + /** + * Returns the redirect url of the payment gateway to redirect the user to + */ + abstract public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string; +} diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index e62cbe54c..e8ede9af7 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -3,10 +3,10 @@ namespace App\Extensions\PaymentGateways\PayPal; use App\Events\CouponUsedEvent; -use App\Helpers\AbstractExtension; use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; use App\Extensions\PaymentGateways\PayPal\PayPalSettings; +use App\Classes\PaymentExtension; use App\Models\PartnerDiscount; use App\Models\Payment; use App\Models\ShopProduct; @@ -28,7 +28,7 @@ /** * Summary of PayPalExtension */ -class PayPalExtension extends AbstractExtension +class PayPalExtension extends PaymentExtension { use CouponTrait; @@ -40,41 +40,8 @@ public static function getConfig(): array ]; } - public function PaypalPay(Request $request): void + public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string { - /** @var User $user */ - $user = Auth::user(); - $shopProduct = ShopProduct::findOrFail($request->shopProduct); - $discount = PartnerDiscount::getDiscount(); - $couponCode = $request->input('couponCode'); - $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); - $price = $shopProduct->price; - - // Coupon Discount. - if ($isValidCoupon->getStatusCode() == 200) { - $price = $this->calcDiscount($price, $isValidCoupon->getData()); - } - - // Partner Discount. - $price = $price - ($price * $discount / 100); - $price = number_format($price, 2); - - // create a new payment - $payment = Payment::create([ - 'user_id' => $user->id, - 'payment_id' => null, - 'payment_method' => 'paypal', - 'type' => $shopProduct->type, - 'status' => 'open', - 'amount' => $shopProduct->quantity, - 'price' => $price, - 'tax_value' => $shopProduct->getTaxValue(), - 'tax_percent' => $shopProduct->getTaxPercent(), - 'total_price' => $shopProduct->getTotalPrice(), - 'currency_code' => $shopProduct->currency_code, - 'shop_item_product_id' => $shopProduct->id, - ]); - $request = new OrdersCreateRequest(); $request->prefer('return=representation'); $request->body = [ @@ -82,14 +49,14 @@ public function PaypalPay(Request $request): void "purchase_units" => [ [ "reference_id" => uniqid(), - "description" => $shopProduct->display . ($discount ? (" (" . __('Discount') . " " . $discount . '%)') : ""), + "description" => $shopProduct->display, "amount" => [ - "value" => $price, + "value" => $totalPriceString, 'currency_code' => strtoupper($shopProduct->currency_code), 'breakdown' => [ 'item_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), - 'value' => $price + 'value' => $totalPriceString ], 'tax_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), @@ -101,12 +68,10 @@ public function PaypalPay(Request $request): void ], "application_context" => [ "cancel_url" => route('payment.Cancel'), - "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id, 'couponCode' => $couponCode]), + "return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id]), 'brand_name' => config('app.name', 'CtrlPanel.GG'), 'shipping_preference' => 'NO_SHIPPING' ] - - ]; try { @@ -123,17 +88,15 @@ public function PaypalPay(Request $request): void throw new \Exception('No redirect link found'); } - Redirect::away($response->result->links[1]->href)->send(); - return; + return $response->result->links[1]->href; } catch (HttpException $ex) { Log::error('PayPal Payment: ' . $ex->getMessage()); - $payment->delete(); - Redirect::route('store.index')->with('error', __('Payment failed'))->send(); - return; + throw new \Exception('PayPal Payment: ' . $ex->getMessage()); } } + static function PaypalSuccess(Request $laravelRequest): void { $user = Auth::user(); @@ -141,7 +104,7 @@ static function PaypalSuccess(Request $laravelRequest): void $payment = Payment::findOrFail($laravelRequest->payment); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); - $couponCode = $laravelRequest->input('couponCode'); + $couponCode = $laravelRequest->input('couponCode'); $request = new OrdersCaptureRequest($laravelRequest->input('token')); $request->prefer('return=representation'); @@ -157,7 +120,7 @@ static function PaypalSuccess(Request $laravelRequest): void ]); if ($couponCode) { - event(new CouponUsedEvent(new Coupon, $couponCode)); + event(new CouponUsedEvent($couponCode)); } event(new UserUpdateCreditsEvent($user)); @@ -193,7 +156,9 @@ static function PaypalSuccess(Request $laravelRequest): void static function getPayPalClient(): PayPalHttpClient { - $environment = env('APP_ENV') == 'local' + error_log(config('app.env')); + + $environment = config('app.env') == 'local' ? new SandboxEnvironment(self::getPaypalClientId(), self::getPaypalClientSecret()) : new ProductionEnvironment(self::getPaypalClientId(), self::getPaypalClientSecret()); return new PayPalHttpClient($environment); @@ -204,7 +169,7 @@ static function getPayPalClient(): PayPalHttpClient static function getPaypalClientId(): string { $settings = new PayPalSettings(); - return env('APP_ENV') == 'local' ? $settings->sandbox_client_id : $settings->client_id; + return config('app.env') == 'local' ? $settings->sandbox_client_id : $settings->client_id; } /** * @return string @@ -212,6 +177,6 @@ static function getPaypalClientId(): string static function getPaypalClientSecret(): string { $settings = new PayPalSettings(); - return env('APP_ENV') == 'local' ? $settings->sandbox_client_secret : $settings->client_secret; + return config('app.env') == 'local' ? $settings->sandbox_client_secret : $settings->client_secret; } } diff --git a/app/Helpers/AbstractExtension.php b/app/Helpers/AbstractExtension.php deleted file mode 100644 index 68af6f88e..000000000 --- a/app/Helpers/AbstractExtension.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -namespace App\Helpers; - -// create a abstract class for the extension that will contain all the methods that will be used in the extension -abstract class AbstractExtension -{ - abstract public static function getConfig(): array; -} diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 1b0d875aa..657150bd5 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin; +use App\Events\CouponUsedEvent; use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; use App\Http\Controllers\Controller; @@ -22,6 +23,7 @@ use App\Settings\CouponSettings; use App\Settings\GeneralSettings; use App\Settings\LocaleSettings; +use Illuminate\Support\Facades\Log; class PaymentController extends Controller { @@ -127,26 +129,64 @@ public function handleFreeProduct(ShopProduct $shopProduct) public function pay(Request $request) { - $product = ShopProduct::find($request->product_id); - $paymentGateway = $request->payment_method; - $coupon_code = $request->coupon_code; - - // on free products, we don't need to use a payment gateway - $realPrice = $product->price - ($product->price * PartnerDiscount::getDiscount() / 100); - if ($realPrice <= 0) { - return $this->handleFreeProduct($product); - } + try { + $user = Auth::user(); + $user = User::findOrFail($user->id); + $productId = $request->product_id; + $shopProduct = ShopProduct::findOrFail($productId); + $discount = PartnerDiscount::getDiscount(); + + + $paymentGateway = $request->payment_method; + $couponCode = $request->coupon_code; + + $subtotal = $shopProduct->price; + + // Apply Coupon + $isCouponValid = $this->isCouponValid($couponCode, $user, $shopProduct->id); + if ($isCouponValid) { + $subtotal = $this->applyCoupon($couponCode, $subtotal); + } - if ($coupon_code) { - return redirect()->route('payment.' . $paymentGateway . 'Pay', [ - 'shopProduct' => $product->id, - 'couponCode' => $coupon_code + // Apply Partner Discount + $subtotal = $subtotal - ($subtotal * $discount / 100); + if ($subtotal <= 0) { + return $this->handleFreeProduct($shopProduct); + } + + // Format the total price to a readable string + $totalPriceString = number_format($subtotal, 2, '.', ''); + + // create a new payment + $payment = Payment::create([ + 'user_id' => $user->id, + 'payment_id' => null, + 'payment_method' => $paymentGateway, + 'type' => $shopProduct->type, + 'status' => 'open', + 'amount' => $shopProduct->quantity, + 'price' => $totalPriceString, + 'tax_value' => $shopProduct->getTaxValue(), + 'tax_percent' => $shopProduct->getTaxPercent(), + 'total_price' => $shopProduct->getTotalPrice(), + 'currency_code' => $shopProduct->currency_code, + 'shop_item_product_id' => $shopProduct->id, ]); + + $paymentGatewayExtension = ExtensionHelper::getExtensionClass($paymentGateway); + $redirectUrl = $paymentGatewayExtension::getRedirectUrl($payment, $shopProduct, $totalPriceString); + event(new CouponUsedEvent($couponCode)); + } catch (Exception $e) { + Log::error($e->getMessage()); + return redirect()->route('store.index')->with('error', __('Oops, something went wrong! Please try again later.')); } - return redirect()->route('payment.' . $paymentGateway . 'Pay', ['shopProduct' => $product->id]); + return redirect()->away($redirectUrl); } + + + /** * @param Request $request */ From fc84e7d1db7668b42b616a578489f55923ec071a Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 26 May 2023 16:30:55 +0200 Subject: [PATCH 198/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Enhance=20coupon=20?= =?UTF-8?q?validation=20and=20application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Listeners/CouponUsed.php | 20 ++------------- app/Models/Coupon.php | 12 +++++---- app/Traits/Coupon.php | 48 ++++++++++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 25 deletions(-) diff --git a/app/Listeners/CouponUsed.php b/app/Listeners/CouponUsed.php index 7ba8c34b4..c9abb42bc 100644 --- a/app/Listeners/CouponUsed.php +++ b/app/Listeners/CouponUsed.php @@ -30,11 +30,6 @@ public function __construct(CouponSettings $couponSettings) */ public function handle(CouponUsedEvent $event) { - // Always check the authenticity of the coupon. - if (!$this->isValidCoupon($event)) { - return; - } - // Automatically increments the coupon usage. $this->incrementUses($event); @@ -60,18 +55,7 @@ public function handle(CouponUsedEvent $event) */ private function incrementUses(CouponUsedEvent $event) { - $event->coupon->where('code', $event->coupon->code)->increment('uses'); - } - - /** - * It checks that the coupon received from the request really exists. - * - * @param \App\Events\CouponUsedEvent $event - * - * @return bool - */ - private function isValidCoupon(CouponUsedEvent $event): bool - { - return $event->coupon->code === $event->couponCode ? true : false; + $event->coupon->increment('uses'); + $event->coupon->save(); } } diff --git a/app/Models/Coupon.php b/app/Models/Coupon.php index 082e423fe..8cd02621c 100644 --- a/app/Models/Coupon.php +++ b/app/Models/Coupon.php @@ -2,11 +2,13 @@ namespace App\Models; +use App\Settings\CouponSettings; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\LogsActivity; use Carbon\Carbon; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Coupon extends Model { @@ -75,16 +77,16 @@ public function getStatus() /** * Check if a user has already exceeded the uses of a coupon. * - * @param Request $request The request being made. - * @param CouponSettings $coupon_settings The instance of the coupon settings. + * @param User $user The request being made. * * @return bool */ - public function isLimitsUsesReached($requestUser, $coupon_settings): bool + public function isMaxUsesReached($user): bool { - $coupon_uses = $requestUser->coupons()->where('id', $this->id)->count(); + $coupon_settings = new CouponSettings; + $coupon_uses = $user->coupons()->where('id', $this->id)->count(); - return $coupon_uses >= $coupon_settings->max_uses_per_user ? true : false; + return $coupon_uses >= $coupon_settings->max_uses_per_user; } /** diff --git a/app/Traits/Coupon.php b/app/Traits/Coupon.php index 091fa855b..5dd0e7e97 100644 --- a/app/Traits/Coupon.php +++ b/app/Traits/Coupon.php @@ -5,7 +5,7 @@ use App\Settings\CouponSettings; use App\Models\Coupon as CouponModel; use App\Models\ShopProduct; -use Illuminate\Http\Request; +use App\Models\User; use Illuminate\Http\JsonResponse; use stdClass; @@ -40,7 +40,7 @@ public function validateCoupon($requestUser, $couponCode, $productId): JsonRespo return $response; } - if ($coupon->isLimitsUsesReached($requestUser, $coupon_settings)) { + if ($coupon->isMaxUsesReached($requestUser, $coupon_settings)) { $response = response()->json([ 'isValid' => false, 'error' => __('You have reached the maximum uses of this coupon.') @@ -69,6 +69,32 @@ public function validateCoupon($requestUser, $couponCode, $productId): JsonRespo return $response; } + public function isCouponValid(string $couponCode, User $user, string $productId): bool + { + if (is_null($couponCode)) return false; + + $coupon = CouponModel::where('code', $couponCode)->first(); + $shopProduct = ShopProduct::findOrFail($productId); + + if ($coupon->getStatus() == 'USES_LIMIT_REACHED') { + return false; + } + + if ($coupon->getStatus() == 'EXPIRED') { + return false; + } + + if ($coupon->isMaxUsesReached($user)) { + return false; + } + + if ($coupon->type === 'amount' && $coupon->value >= $shopProduct->price) { + return false; + } + + return true; + } + public function calcDiscount($productPrice, stdClass $data) { @@ -89,4 +115,22 @@ public function calcDiscount($productPrice, stdClass $data) return $productPrice; } + + public function applyCoupon(string $couponCode, float $price) + { + $coupon = CouponModel::where('code', $couponCode)->first(); + + if ($coupon->type === 'percentage') { + return $price - ($price * $coupon->value / 100); + } + + if ($coupon->type === 'amount') { + // There is no discount if the value of the coupon is greater than or equal to the value of the product. + if ($coupon->value >= $price) { + return $price; + } + } + + return $price - $coupon->value; + } } From 4873992c4853acd5dc561194192758c1165dc5f2 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 26 May 2023 16:31:23 +0200 Subject: [PATCH 199/514] =?UTF-8?q?refactor:=20=E2=9A=A1=EF=B8=8F=20remove?= =?UTF-8?q?=20coupon=20instance=20from=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Events/CouponUsedEvent.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Events/CouponUsedEvent.php b/app/Events/CouponUsedEvent.php index ceb0ac376..54b322bd9 100644 --- a/app/Events/CouponUsedEvent.php +++ b/app/Events/CouponUsedEvent.php @@ -19,9 +19,10 @@ class CouponUsedEvent * * @return void */ - public function __construct(Coupon $coupon, string $couponCode) + public function __construct(string $couponCode) { - $this->coupon = $coupon; + $this->couponCode = $couponCode; + $this->coupon = Coupon::where('code', $couponCode)->first(); } } From 48e5cae9a1d1d57dba9385ff6cf7b26795de2127 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 28 May 2023 02:27:39 +0200 Subject: [PATCH 200/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Migrate=20other=20p?= =?UTF-8?q?ayment=20gateways=20to=20new=20payment=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Mollie/MollieExtension.php | 60 +++---------------- .../PayPal/PayPalExtension.php | 1 - .../Stripe/StripeExtension.php | 60 ++++--------------- .../Controllers/Admin/PaymentController.php | 3 - 4 files changed, 19 insertions(+), 105 deletions(-) diff --git a/app/Extensions/PaymentGateways/Mollie/MollieExtension.php b/app/Extensions/PaymentGateways/Mollie/MollieExtension.php index 6edd34112..a2bc7054f 100644 --- a/app/Extensions/PaymentGateways/Mollie/MollieExtension.php +++ b/app/Extensions/PaymentGateways/Mollie/MollieExtension.php @@ -2,7 +2,7 @@ namespace App\Extensions\PaymentGateways\Mollie; -use App\Helpers\AbstractExtension; +use App\Classes\AbstractExtension; use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; use App\Models\PartnerDiscount; @@ -37,43 +37,10 @@ public static function getConfig(): array ]; } - public function pay(Request $request): void + public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string { $url = 'https://api.mollie.com/v2/payments'; $settings = new MollieSettings(); - - $user = Auth::user(); - $shopProduct = ShopProduct::findOrFail($request->shopProduct); - $discount = PartnerDiscount::getDiscount(); - $couponCode = $request->input('couponCode'); - $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); - $price = $shopProduct->price; - - // Coupon Discount. - if ($isValidCoupon->getStatusCode() == 200) { - $price = $this->calcDiscount($price, $isValidCoupon->getData()); - } - - // Partner Discount. - $price = $price - ($price * $discount / 100); - $price = number_format($price, 2, thousands_separator: ''); - - // create a new payment - $payment = Payment::create([ - 'user_id' => $user->id, - 'payment_id' => null, - 'payment_method' => 'mollie', - 'type' => $shopProduct->type, - 'status' => 'open', - 'amount' => $shopProduct->quantity, - 'price' => $price, - 'tax_value' => $shopProduct->getTaxValue(), - 'tax_percent' => $shopProduct->getTaxPercent(), - 'total_price' => $shopProduct->getTotalPrice(), - 'currency_code' => $shopProduct->currency_code, - 'shop_item_product_id' => $shopProduct->id, - ]); - try { $response = Http::withHeaders([ 'Content-Type' => 'application/json', @@ -81,10 +48,10 @@ public function pay(Request $request): void ])->post($url, [ 'amount' => [ 'currency' => $shopProduct->currency_code, - 'value' => $price, + 'value' => $totalPriceString, ], 'description' => "Order #{$payment->id} - " . $shopProduct->name, - 'redirectUrl' => route('payment.MollieSuccess', ['couponCode' => $couponCode]), + 'redirectUrl' => route('payment.MollieSuccess'), 'cancelUrl' => route('payment.Cancel'), 'webhookUrl' => url('/extensions/payment/MollieWebhook'), 'metadata' => [ @@ -94,24 +61,13 @@ public function pay(Request $request): void if ($response->status() != 201) { Log::error('Mollie Payment: ' . $response->body()); - $payment->delete(); - - Redirect::route('store.index')->with('error', __('Payment failed'))->send(); - return; + throw new Exception('Payment failed'); } - $payment->update([ - 'payment_id' => $response->json()['id'], - ]); - - Redirect::away($response->json()['_links']['checkout']['href'])->send(); - return; + return $response->json()['_links']['checkout']['href']; } catch (Exception $ex) { Log::error('Mollie Payment: ' . $ex->getMessage()); - $payment->delete(); - - Redirect::route('store.index')->with('error', __('Payment failed'))->send(); - return; + throw new Exception('Payment failed'); } } @@ -122,7 +78,7 @@ static function success(Request $request): void $couponCode = $request->input('couponCode'); if ($couponCode) { - event(new CouponUsedEvent(new Coupon, $couponCode)); + event(new CouponUsedEvent($couponCode)); } Redirect::route('home')->with('success', 'Your payment is being processed')->send(); diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index e8ede9af7..bef9b5304 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -156,7 +156,6 @@ static function PaypalSuccess(Request $laravelRequest): void static function getPayPalClient(): PayPalHttpClient { - error_log(config('app.env')); $environment = config('app.env') == 'local' ? new SandboxEnvironment(self::getPaypalClientId(), self::getPaypalClientSecret()) diff --git a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php index b66655932..2c975232d 100644 --- a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php +++ b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php @@ -2,7 +2,7 @@ namespace App\Extensions\PaymentGateways\Stripe; -use App\Helpers\AbstractExtension; +use App\Classes\AbstractExtension; use App\Events\PaymentEvent; use App\Events\CouponUsedEvent; use App\Events\UserUpdateCreditsEvent; @@ -36,52 +36,14 @@ public static function getConfig(): array ]; } - /** - * @param Request $request - * @param ShopProduct $shopProduct - */ - public function StripePay(Request $request) + public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string { - $user = Auth::user(); - $shopProduct = ShopProduct::findOrFail($request->shopProduct); - $price = $shopProduct->price; - - // check if the price is valid for stripe - if (!self::checkPriceAmount($shopProduct->getTotalPrice(), strtoupper($shopProduct->currency_code), 'stripe')) { - Redirect::route('home')->with('error', __('The product you chose can\'t be purchased with this payment method. The total amount is too small. Please buy a bigger amount or try a different payment method.'))->send(); - return; + // check if the total price is valid for stripe + $totalPriceNumber = floatval($totalPriceString); + if (!self::checkPriceAmount($totalPriceNumber, strtoupper($shopProduct->currency_code), 'stripe')) { + throw new Exception('Invalid price amount'); } - $discount = PartnerDiscount::getDiscount(); - $couponCode = $request->input('couponCode'); - $isValidCoupon = $this->validateCoupon($request->user(), $couponCode, $request->shopProduct); - - // Coupon Discount. - if ($isValidCoupon->getStatusCode() == 200) { - $price = $this->calcDiscount($price, $isValidCoupon->getData()); - } - - // Partner Discount. - $price = $price - ($price * $discount / 100); - $price = number_format($price, 2); - - - // create payment - $payment = Payment::create([ - 'user_id' => $user->id, - 'payment_id' => null, - 'payment_method' => 'stripe', - 'type' => $shopProduct->type, - 'status' => 'open', - 'amount' => $shopProduct->quantity, - 'price' => $price, - 'tax_value' => $shopProduct->getTaxValue(), - 'total_price' => $shopProduct->getTotalPrice(), - 'tax_percent' => $shopProduct->getTaxPercent(), - 'currency_code' => $shopProduct->currency_code, - 'shop_item_product_id' => $shopProduct->id, - ]); - $stripeClient = self::getStripeClient(); $request = $stripeClient->checkout->sessions->create([ 'line_items' => [ @@ -89,10 +51,10 @@ public function StripePay(Request $request) 'price_data' => [ 'currency' => $shopProduct->currency_code, 'product_data' => [ - 'name' => $shopProduct->display . ($discount ? (' (' . __('Discount') . ' ' . $discount . '%)') : ''), + 'name' => $shopProduct->display, 'description' => $shopProduct->description, ], - 'unit_amount_decimal' => $price, + 'unit_amount_decimal' => $totalPriceString, ], 'quantity' => 1, ], @@ -110,7 +72,7 @@ public function StripePay(Request $request) ], 'mode' => 'payment', - 'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id, 'couponCode' => $couponCode]) . '&session_id={CHECKOUT_SESSION_ID}', + 'success_url' => route('payment.StripeSuccess', ['payment' => $payment->id]) . '&session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => route('payment.Cancel'), 'payment_intent_data' => [ 'metadata' => [ @@ -119,7 +81,7 @@ public function StripePay(Request $request) ], ]); - Redirect::to($request->url)->send(); + return $request->url; } /** @@ -302,7 +264,7 @@ public static function getStripeEndpointSecret() * @return bool * @description check if the amount is higher than the minimum amount for the stripe gateway */ - public static function checkPriceAmount($amount, $currencyCode, $payment_method) + public static function checkPriceAmount(float $amount, string $currencyCode, string $payment_method) { $minimums = [ "USD" => [ diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 657150bd5..850f5a93d 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -184,9 +184,6 @@ public function pay(Request $request) return redirect()->away($redirectUrl); } - - - /** * @param Request $request */ From 2161464ee38c7f9cee03ec8c2b82b10cf70631d9 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 28 May 2023 02:34:59 +0200 Subject: [PATCH 201/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Added=20Enum=20for?= =?UTF-8?q?=20payment=20states?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Enums/PaymentStatus.php | 12 ++++++++++++ .../PaymentGateways/Mollie/MollieExtension.php | 16 ++++++---------- .../PaymentGateways/PayPal/PayPalExtension.php | 11 ++++------- .../PaymentGateways/Stripe/StripeExtension.php | 13 ++++--------- app/Listeners/UserPayment.php | 3 ++- 5 files changed, 28 insertions(+), 27 deletions(-) create mode 100644 app/Enums/PaymentStatus.php diff --git a/app/Enums/PaymentStatus.php b/app/Enums/PaymentStatus.php new file mode 100644 index 000000000..eb01dbddc --- /dev/null +++ b/app/Enums/PaymentStatus.php @@ -0,0 +1,12 @@ +<?php + +namespace App\Enums; + +// Payment status are open, processing, paid and canceled +enum PaymentStatus: String +{ + case OPEN = "open"; + case PROCESSING = "processing"; + case PAID = "paid"; + case CANCELED = "canceled"; +} diff --git a/app/Extensions/PaymentGateways/Mollie/MollieExtension.php b/app/Extensions/PaymentGateways/Mollie/MollieExtension.php index a2bc7054f..08d4480d7 100644 --- a/app/Extensions/PaymentGateways/Mollie/MollieExtension.php +++ b/app/Extensions/PaymentGateways/Mollie/MollieExtension.php @@ -3,6 +3,7 @@ namespace App\Extensions\PaymentGateways\Mollie; use App\Classes\AbstractExtension; +use App\Enums\PaymentStatus; use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; use App\Models\PartnerDiscount; @@ -74,12 +75,8 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct static function success(Request $request): void { $payment = Payment::findOrFail($request->input('payment')); - $payment->status = 'pending'; - $couponCode = $request->input('couponCode'); - - if ($couponCode) { - event(new CouponUsedEvent($couponCode)); - } + $payment->status = PaymentStatus::PROCESSING; + $payment->save(); Redirect::route('home')->with('success', 'Your payment is being processed')->send(); return; @@ -100,16 +97,15 @@ static function webhook(Request $request): JsonResponse return response()->json(['success' => false]); } - $payment = Payment::findOrFail($response->json()['metadata']['payment_id']); - $payment->status->update([ - 'status' => $response->json()['status'], - ]); + $payment = Payment::findOrFail($response->json()['metadata']['payment_id']); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); event(new PaymentEvent($payment, $payment, $shopProduct)); if ($response->json()['status'] == 'paid') { $user = User::findOrFail($payment->user_id); + $payment->status = PaymentStatus::PAID; + $payment->save(); event(new UserUpdateCreditsEvent($user)); } } catch (Exception $ex) { diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index bef9b5304..981d86ef2 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -7,6 +7,7 @@ use App\Events\UserUpdateCreditsEvent; use App\Extensions\PaymentGateways\PayPal\PayPalSettings; use App\Classes\PaymentExtension; +use App\Enums\PaymentStatus; use App\Models\PartnerDiscount; use App\Models\Payment; use App\Models\ShopProduct; @@ -104,7 +105,6 @@ static function PaypalSuccess(Request $laravelRequest): void $payment = Payment::findOrFail($laravelRequest->payment); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); - $couponCode = $laravelRequest->input('couponCode'); $request = new OrdersCaptureRequest($laravelRequest->input('token')); $request->prefer('return=representation'); @@ -115,13 +115,10 @@ static function PaypalSuccess(Request $laravelRequest): void if ($response->statusCode == 201 || $response->statusCode == 200) { //update payment $payment->update([ - 'status' => 'paid', + 'status' => PaymentStatus::PAID, 'payment_id' => $response->result->id, ]); - if ($couponCode) { - event(new CouponUsedEvent($couponCode)); - } event(new UserUpdateCreditsEvent($user)); event(new PaymentEvent($user, $payment, $shopProduct)); @@ -134,7 +131,7 @@ static function PaypalSuccess(Request $laravelRequest): void dd($response); } else { $payment->update([ - 'status' => 'cancelled', + 'status' => PaymentStatus::CANCELED, 'payment_id' => $response->result->id, ]); abort(500); @@ -146,7 +143,7 @@ static function PaypalSuccess(Request $laravelRequest): void dd($ex->getMessage()); } else { $payment->update([ - 'status' => 'cancelled', + 'status' => PaymentStatus::CANCELED, 'payment_id' => $response->result->id, ]); abort(422); diff --git a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php index 2c975232d..b96134317 100644 --- a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php +++ b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php @@ -3,6 +3,7 @@ namespace App\Extensions\PaymentGateways\Stripe; use App\Classes\AbstractExtension; +use App\Enums\PaymentStatus; use App\Events\PaymentEvent; use App\Events\CouponUsedEvent; use App\Events\UserUpdateCreditsEvent; @@ -93,7 +94,6 @@ public static function StripeSuccess(Request $request) $user = User::findOrFail($user->id); $payment = Payment::findOrFail($request->input('payment')); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); - $couponCode = $request->input('couponCode'); Redirect::route('home')->with('success', 'Please wait for success')->send(); @@ -112,16 +112,11 @@ public static function StripeSuccess(Request $request) //update payment $payment->update([ 'payment_id' => $paymentSession->payment_intent, - 'status' => 'paid', + 'status' => PaymentStatus::PAID, ]); - if ($couponCode) { - event(new CouponUsedEvent(new Coupon, $couponCode)); - } - //payment notification $user->notify(new ConfirmPaymentNotification($payment)); - event(new UserUpdateCreditsEvent($user)); event(new PaymentEvent($user, $payment, $shopProduct)); @@ -133,7 +128,7 @@ public static function StripeSuccess(Request $request) //update payment $payment->update([ 'payment_id' => $paymentSession->payment_intent, - 'status' => 'processing', + 'status' => PaymentStatus::PROCESSING, ]); event(new PaymentEvent($user, $payment, $shopProduct)); @@ -174,7 +169,7 @@ public static function handleStripePaymentSuccessHook($paymentIntent) //update payment db entry status $payment->update([ 'payment_id' => $payment->payment_id ?? $paymentIntent->id, - 'status' => 'paid' + 'status' => PaymentStatus::PAID, ]); //payment notification diff --git a/app/Listeners/UserPayment.php b/app/Listeners/UserPayment.php index 3417a7ce2..9e1066ef1 100644 --- a/app/Listeners/UserPayment.php +++ b/app/Listeners/UserPayment.php @@ -2,6 +2,7 @@ namespace App\Listeners; +use App\Enums\PaymentStatus; use App\Events\PaymentEvent; use App\Models\User; use Illuminate\Support\Facades\DB; @@ -48,7 +49,7 @@ public function handle(PaymentEvent $event) $shopProduct = $event->shopProduct; // only update user if payment is paid - if ($event->payment->status != "paid") { + if ($event->payment->status != PaymentStatus::PAID) { return; } From d365b122db41cbd84ece2568e5e106f31b458fa7 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 2 Jun 2023 14:54:02 +0200 Subject: [PATCH 202/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Coupon=20frontend?= =?UTF-8?q?,=20calculation=20and=20submitting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/store/checkout.blade.php | 363 +++++++++--------- 1 file changed, 181 insertions(+), 182 deletions(-) diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index 5fc360040..5d5de5cfd 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -22,104 +22,87 @@ <!-- END CONTENT HEADER --> <!-- MAIN CONTENT --> - <section class="content"> + <section class="content" x-data="couponForm()"> <div class="container-fluid"> - <form - id="payment_form" - action="{{ route('payment.pay') }}" - method="POST" - x-data="{ - payment_method: '', - coupon_code: '', - clicked: false, - setCouponCode(event) { - this.coupon_code = event.target.value - } - }" - > + <form id="payment_form" action="{{ route('payment.pay') }}" method="POST"> @csrf @method('post') <div class="row d-flex justify-content-center flex-wrap"> @if (!$productIsFree) <div class="col-xl-4"> <div class="card"> - <div class="card-header"> - <h4 class="mb-0"> - <i class="fas fa-money-check-alt"></i> - Payment Methods - </h4> - </div> + <div class="card-body"> - <input type="hidden" name="product_id" value="{{ $product->id }}"> - <input type="hidden" name="payment_method" :value="payment_method" - x-model="payment_method"> - <div class="row"> - <div class="col-lg-12"> - @foreach ($paymentGateways as $gateway) - <div class="row checkout-gateways"> - <div class="col-12 d-flex justify-content-between"> - <label class="form-check-label h4 checkout-gateway-label" - for="{{ $gateway->name }}"> - <span class="mr-3">{{ $gateway->name }}</span> - </label> - <button class="btn btn-primary rounded" type="button" - name="payment-method" id="{{ $gateway->name }}" - value="{{ $gateway->name }}" - :class="payment_method === '{{ $gateway->name }}' ? - 'active' : ''" - @click="payment_method = '{{ $gateway->name }}'; clicked = true;" - x-text="payment_method == '{{ $gateway->name }}' ? 'Selected' : 'Select'">Select</button> - </button> + <ul class="list-group "> + <li class="list-group-item"> + <div class="row"> + <input type="hidden" name="product_id" value="{{ $product->id }}"> + <input type="hidden" name="payment_method" :value="payment_method" + x-model="payment_method"> + <div class="col-lg-12"> + <span class="h4">{{ __('Payment Methods') }}</span> + <div class="mt-2"> + @foreach ($paymentGateways as $gateway) + <div + class="row checkout-gateways @if (!$loop->last) mb-2 @endif"> + <div class="col-12 d-flex justify-content-between"> + <label + class="form-check-label h5 checkout-gateway-label" + for="{{ $gateway->name }}"> + <span class="mr-3">{{ $gateway->name }}</span> + </label> + <button class="btn btn-primary rounded" type="button" + name="payment-method" id="{{ $gateway->name }}" + value="{{ $gateway->name }}" + :class="payment_method === '{{ $gateway->name }}' ? + 'active' : ''" + @click="payment_method = '{{ $gateway->name }}'; submitted = true;" + x-text="payment_method == '{{ $gateway->name }}' ? 'Selected' : 'Select'">Select</button> + </button> + </div> + </div> + @endforeach </div> </div> - @endforeach - </div> - </div> + </div> + </li> + + <li class="list-group-item"> + <div class="row"> + <div class="col-lg-12"> + @if ($isCouponsEnabled) + <span class="h4">{{ __('Coupon') }}</span> + + <div class="d-flex mt-2"> + <input type="text" id="coupon_code" name="coupon_code" + value="{{ old('coupon_code') }}" :value="coupon_code" + class="form-control @error('coupon_code') is_invalid @enderror" + placeholder="{{ __('Enter your coupon here...') }}" + x-on:change.debounce="setCouponCode($event)" + x-model="coupon_code" /> + <button type="button" id="send_coupon_code" + @click="checkCoupon()" class="btn btn-success ml-3" + :disabled="!coupon_code.length" + :class="!coupon_code.length ? 'disabled' : ''" + :value="coupon_code"> + {{ __('Submit') }} + </button> + + </div> + @error('coupon_code') + <div class="text-danger"> + {{ $message }} + </div> + @enderror + @endif + </div> + </div> + </li> + </ul> </div> </div> </div> - @if ($isCouponsEnabled) - <div class="col-xl-4"> - <div class="card"> - <div class="card-header"> - <h4 class="mb-0"> - Coupon Code - </h4> - </div> - <div class="card-body"> - <div class="d-flex"> - <input - type="text" - id="coupon_code" - name="coupon_code" - value="{{ old('coupon_code') }}" - :value="coupon_code" - class="form-control @error('coupon_code') is_invalid @enderror" - placeholder="SUMMER" - x-on:change.debounce="setCouponCode($event)" - x-model="coupon_code" - /> - <button - type="button" - id="send_coupon_code" - class="btn btn-success ml-3" - :disabled="!coupon_code.length" - :class="!coupon_code.length ? 'disabled' : ''" - :value="coupon_code" - > - {{ __('Submit') }} - </button> - </div> - @error('coupon_code') - <div class="text-danger"> - {{ $message }} - </div> - @enderror - </div> - </div> - </div> - @endif @endif <div class="col-xl-3"> <div class="card"> @@ -173,7 +156,7 @@ class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $ <li class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Subtotal') }}</span> <span class="text-muted d-inline-block"> - {{ $product->formatToCurrency($discountedprice) }}</span> + {{ $product->formatToCurrency($product->price) }}</span> </li> <div class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Tax') }} @@ -184,18 +167,19 @@ class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $ <span class="text-muted d-inline-block"> + {{ $product->formatToCurrency($taxvalue) }}</span> </div> - <div id="coupon_discount_details" class="d-flex justify-content-between" style="display: none !important;"> - <span class="text-muted d-inline-block"> - {{ __('Coupon Discount') }} - </span> - <span id="coupon_discount_value" class="text-muted d-inline-block"> + <div id="coupon_discount_details" class="d-flex justify-content-between" + style="display: none !important;"> + <span class="text-muted d-inline-block"> + {{ __('Coupon Discount') }} + </span> + <span id="coupon_discount_value" class="text-muted d-inline-block"> - </span> + </span> </div> @if ($discountpercent && $discountvalue) <div class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Discount') }} - ({{ $discountpercent }}%):</span> + ({{ $discountpercent }}%)</span> <span class="text-muted d-inline-block">-{{ $product->formatToCurrency($discountvalue) }}</span> </div> @@ -203,40 +187,36 @@ class="text-muted d-inline-block">-{{ $product->formatToCurrency($discountvalue) <hr class="text-white border-secondary"> <div class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Total') }}</span> - <input id="total_price_input" type="hidden" value="{{ $product->getTotalPrice() }}"> - <span - id="total_price" - class="text-muted d-inline-block" - > - {{ $product->formatToCurrency($total) }} + <input id="total_price_input" type="hidden" x-model="totalPrice"> + <span class="text-muted d-inline-block" x-text="totalPrice"> </span> </div> <template x-if="payment_method"> <div class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Pay with') }}</span> - <span class="text-muted d-inline-block" x-text="payment_method"></span> + <span class="text-muted d-inline-block" + x-text="payment_method"></span> </div> </template> </ul> </li> </ul> - <button :disabled="(!payment_method || !clicked || coupon_code ? true : false) && {{ !$productIsFree }}" + <button + :disabled="(!payment_method || !clicked || coupon_code) && + {{ !$productIsFree }}" id="submit_form_button" - :class="(!payment_method || !clicked || coupon_code ? true : false) && {{ !$productIsFree }} ? 'disabled' : ''" - :x-text="coupon_code" - class="btn btn-success float-right w-100"> - + :class="(!payment_method || !clicked || coupon_code) && + {{ !$productIsFree }} ? 'disabled' : ''" + :x-text="coupon_code" class="btn btn-success float-right w-100"> <i class="far fa-credit-card mr-2" @click="clicked == true"></i> @if ($productIsFree) {{ __('Get for free') }} @else {{ __('Submit Payment') }} @endif - </button> - <script> - </script> + <script></script> </div> </div> </div> @@ -248,81 +228,100 @@ class="btn btn-success float-right w-100"> <!-- END CONTENT --> <script> - $(document).ready(function() { - const productId = $("[name='product_id']").val() - let hasCouponCodeValue = $('#coupon_code').val().trim() !== '' - - $('#coupon_code').on('change', function(e) { - hasCouponCodeValue = e.target.value !== '' - }) - - function calcPriceWithCouponDiscount(couponValue, couponType) { - let totalPrice = $('#total_price_input').val() - - if (typeof totalPrice == 'string') { - totalPrice = parseFloat(totalPrice) - } - - if (couponType === 'percentage') { - totalPrice = totalPrice - (totalPrice * couponValue / 100) - $('#coupon_discount_value').text("- " + couponValue + "%") - } else if (couponType === 'amount') { - totalPrice = totalPrice - couponValue - $('#coupon_discount_value').text(totalPrice) - } - - $('#total_price').text(totalPrice) - $('#total_price_input').val(totalPrice) - } + function couponForm() { + console.log("{{ $discountedprice }}", " {{ $discountpercent }}", "{{ $discountvalue }}", + " {{ $taxpercent }}", "{{ $taxvalue }}", "{{ $productIsFree }}", "{{ $total }}") + return { + // Get the product id from the url + productId: window.location.pathname.split('/').pop(), + payment_method: '', + coupon_code: '', + submitted: false, + totalPrice: {{ $discountedprice }}, + + + setCouponCode(event) { + this.coupon_code = event.target.value + console.log(event.target.value) + }, + + async checkCoupon() { + console.log(this.coupon_code) + const response = await (fetch( + "{{ route('admin.coupon.redeem') }}", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr( + 'content') + }, + body: JSON.stringify({ + couponCode: this.coupon_code, + productId: this.productId + }) + } + ) + .then(response => response.json()).catch((error) => { + Swal.fire({ + icon: 'error', + title: 'Oops...', + text: "{{ __('The coupon code you entered is invalid.') }}" + }) + })) + + if (response.isValid && response.couponCode) { + Swal.fire({ + icon: 'success', + text: "{{ __('The coupon was successfully added to your purchase.') }}" + + }) - function checkCoupon() { - const couponCode = $('#coupon_code').val() - - $.ajax({ - url: "{{ route('admin.coupon.redeem') }}", - method: 'POST', - data: { couponCode: couponCode, productId: productId }, - success: function(response) { - if (response.isValid && response.couponCode) { - Swal.fire({ - icon: 'success', - text: 'The coupon was successfully added to your purchase.', - }).then(function(isConfirmed) { - calcPriceWithCouponDiscount(response.couponValue, response.couponType) - $('#submit_form_button').prop('disabled', false).removeClass('disabled') - $('#send_coupon_code').prop('disabled', true) - $('#coupon_discount_details').prop('disabled', false).show() - }) - - } else { - console.log('Invalid Coupon') - } - }, - error: function(response) { - const responseJson = response.responseJSON - - if (!responseJson.isValid) { - Swal.fire({ - icon: 'error', - title: 'Oops...', - text: responseJson.error, - }) - } - } - }) - } - - $('#payment_form').on('submit', function(e) { - if (hasCouponCodeValue) { - checkCoupon() - } - }) - - $('#send_coupon_code').click(function(e) { - if (hasCouponCodeValue) { - checkCoupon() - } - }) - }) + this.calcPriceWithCouponDiscount(response.couponValue, response + .couponType) + + $('#submit_form_button').prop('disabled', false).removeClass( + 'disabled') + $('#send_coupon_code').prop('disabled', true) + $('#coupon_discount_details').prop('disabled', false).show() + + } else { + Swal.fire({ + icon: 'error', + title: 'Oops...', + text: "{{ __('The coupon code you entered is invalid.') }}" + }) + } + + + }, + + calcPriceWithCouponDiscount(couponValue, couponType) { + let newTotalPrice = this.totalPrice + + if (couponType === 'percentage') { + newTotalPrice = newTotalPrice - (newTotalPrice * couponValue / 100) + $('#coupon_discount_value').text("- " + couponValue + "%") + } else if (couponType === 'amount') { + newTotalPrice = totanewTotalPricelPrice - couponValue + $('#coupon_discount_value').text(this.totalPrice) + } + + // get language for formatting currency + const lang = "{{ app()->getLocale() }}" + console.log(lang) + // format totalPrice to currency + this.totalPrice = newTotalPrice.toLocaleString(lang, { + style: 'currency', + currency: "{{ $product->currency_code }}", + }) + + console.log(newTotalPrice) + console.log(this.totalPrice) + + $('#total_price_input').val(this.totalPrice) + }, + + } + } </script> @endsection From 2c2279af56a2b141d4b7cc3d340da57cfca6f1c0 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 2 Jun 2023 14:59:31 +0200 Subject: [PATCH 203/514] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20remove?= =?UTF-8?q?=20jquery=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/store/checkout.blade.php | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index 5d5de5cfd..c9308548a 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -172,7 +172,7 @@ class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $ <span class="text-muted d-inline-block"> {{ __('Coupon Discount') }} </span> - <span id="coupon_discount_value" class="text-muted d-inline-block"> + <span x-text="couponDiscountedValue" class="text-muted d-inline-block"> </span> </div> @@ -188,7 +188,8 @@ class="text-muted d-inline-block">-{{ $product->formatToCurrency($discountvalue) <div class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Total') }}</span> <input id="total_price_input" type="hidden" x-model="totalPrice"> - <span class="text-muted d-inline-block" x-text="totalPrice"> + <span class="text-muted d-inline-block" + x-text="formatToCurrency(totalPrice)"> </span> </div> <template x-if="payment_method"> @@ -229,8 +230,6 @@ class="text-muted d-inline-block">-{{ $product->formatToCurrency($discountvalue) <script> function couponForm() { - console.log("{{ $discountedprice }}", " {{ $discountpercent }}", "{{ $discountvalue }}", - " {{ $taxpercent }}", "{{ $taxvalue }}", "{{ $productIsFree }}", "{{ $total }}") return { // Get the product id from the url productId: window.location.pathname.split('/').pop(), @@ -238,15 +237,14 @@ function couponForm() { coupon_code: '', submitted: false, totalPrice: {{ $discountedprice }}, + couponDiscountedValue: 0, setCouponCode(event) { this.coupon_code = event.target.value - console.log(event.target.value) }, async checkCoupon() { - console.log(this.coupon_code) const response = await (fetch( "{{ route('admin.coupon.redeem') }}", { method: 'POST', @@ -291,34 +289,36 @@ function couponForm() { text: "{{ __('The coupon code you entered is invalid.') }}" }) } + }, - }, calcPriceWithCouponDiscount(couponValue, couponType) { let newTotalPrice = this.totalPrice if (couponType === 'percentage') { newTotalPrice = newTotalPrice - (newTotalPrice * couponValue / 100) - $('#coupon_discount_value').text("- " + couponValue + "%") + this.couponDiscountedValue = "- " + couponValue + "%" } else if (couponType === 'amount') { newTotalPrice = totanewTotalPricelPrice - couponValue - $('#coupon_discount_value').text(this.totalPrice) + this.couponDiscountedValue = "- " + couponValue + " {{ $product->currency_code }}" } + // get language for formatting currency + const lang = "{{ app()->getLocale() }}" + // format totalPrice to currency + this.totalPrice = this.formatToCurrency(newTotalPrice) + }, + + formatToCurrency(amount) { // get language for formatting currency const lang = "{{ app()->getLocale() }}" console.log(lang) // format totalPrice to currency - this.totalPrice = newTotalPrice.toLocaleString(lang, { + return amount.toLocaleString(lang, { style: 'currency', currency: "{{ $product->currency_code }}", }) - - console.log(newTotalPrice) - console.log(this.totalPrice) - - $('#total_price_input').val(this.totalPrice) }, } From 416e2f6d000c0fb178a3bcbd82155062138568d3 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Tue, 6 Jun 2023 15:48:26 +0200 Subject: [PATCH 204/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Show=20coupon=20r?= =?UTF-8?q?esult=20with=20amount?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/store/checkout.blade.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index c9308548a..20e8bf91c 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -178,7 +178,7 @@ class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $ </div> @if ($discountpercent && $discountvalue) <div class="d-flex justify-content-between"> - <span class="text-muted d-inline-block">{{ __('Discount') }} + <span class="text-muted d-inline-block">{{ __('Partner Discount') }} ({{ $discountpercent }}%)</span> <span class="text-muted d-inline-block">-{{ $product->formatToCurrency($discountvalue) }}</span> @@ -296,24 +296,26 @@ function couponForm() { calcPriceWithCouponDiscount(couponValue, couponType) { let newTotalPrice = this.totalPrice + + console.log(couponType) if (couponType === 'percentage') { newTotalPrice = newTotalPrice - (newTotalPrice * couponValue / 100) this.couponDiscountedValue = "- " + couponValue + "%" } else if (couponType === 'amount') { - newTotalPrice = totanewTotalPricelPrice - couponValue - this.couponDiscountedValue = "- " + couponValue + " {{ $product->currency_code }}" + + newTotalPrice = newTotalPrice - couponValue + this.couponDiscountedValue = "- " + this.formatToCurrency(couponValue) } - // get language for formatting currency - const lang = "{{ app()->getLocale() }}" // format totalPrice to currency this.totalPrice = this.formatToCurrency(newTotalPrice) }, formatToCurrency(amount) { - // get language for formatting currency - const lang = "{{ app()->getLocale() }}" - console.log(lang) + // get language for formatting currency - use en_US as product->formatToCurrency() uses it + //const lang = "{{ app()->getLocale() }}" + const lang = 'en-US' + // format totalPrice to currency return amount.toLocaleString(lang, { style: 'currency', From 466f903209e118d7949f9cf7b23e994a892a3efa Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Tue, 6 Jun 2023 15:50:46 +0200 Subject: [PATCH 205/514] =?UTF-8?q?fix:=20=F0=9F=A5=85=20Extend=20error=20?= =?UTF-8?q?message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/default/views/store/checkout.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index 20e8bf91c..b6a002940 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -263,7 +263,7 @@ function couponForm() { Swal.fire({ icon: 'error', title: 'Oops...', - text: "{{ __('The coupon code you entered is invalid.') }}" + text: "{{ __('The coupon code you entered is invalid or cannot be applied to this product.') }}" }) })) @@ -286,7 +286,7 @@ function couponForm() { Swal.fire({ icon: 'error', title: 'Oops...', - text: "{{ __('The coupon code you entered is invalid.') }}" + text: "{{ __('The coupon code you entered is invalid or cannot be applied to this product.') }}" }) } }, From 5027986b001d59076bb95ef2650bfc1776d26f21 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Tue, 6 Jun 2023 15:52:04 +0200 Subject: [PATCH 206/514] chore: remove old file --- Addon-notes.md | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 Addon-notes.md diff --git a/Addon-notes.md b/Addon-notes.md deleted file mode 100644 index ac00939be..000000000 --- a/Addon-notes.md +++ /dev/null @@ -1,4 +0,0 @@ -Export diff files: -Commit Hash of lates Main commit - -git diff -r --no-commit-id --name-only --diff-filter=ACMR \<commit> | tar -czf \.\./controllpanelgg-monthly-addon/file.tgz -T - From af8eb7b1a3ac96de2ddda8cea634880b4dd1cb27 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Tue, 6 Jun 2023 17:04:36 +0200 Subject: [PATCH 207/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20fix=20text=20typo?= =?UTF-8?q?=20and=20add=20localisation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/en.json | 67 ++++++++++++++++--- themes/default/views/store/checkout.blade.php | 5 +- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/lang/en.json b/lang/en.json index 90f23aae0..69316d6ef 100644 --- a/lang/en.json +++ b/lang/en.json @@ -236,7 +236,6 @@ "You usually do not need to change anything here": "You usually do not need to change anything here", "Edit Server": "Edit Server", "Server identifier": "Server identifier", - "User": "User", "Server id": "Server id", "Config": "Config", "Suspended at": "Suspended at", @@ -355,7 +354,6 @@ "Useful Links": "Useful Links", "Icon class name": "Icon class name", "You can find available free icons": "You can find available free icons", - "Title": "Title", "Link": "Link", "description": "description", "Icon": "Icon", @@ -385,11 +383,9 @@ "Content": "Content", "Server limit": "Server limit", "Discord": "Discord", - "Usage": "Usage", "IP": "IP", "Referals": "Referals", "referral-code": "referral-code", - "Vouchers": "Vouchers", "Voucher details": "Voucher details", "Summer break voucher": "Summer break voucher", "Code": "Code", @@ -452,7 +448,6 @@ "Blacklist List": "Blacklist List", "Reason": "Reason", "Created At": "Created At", - "Actions": "Actions", "Add To Blacklist": "Add To Blacklist", "please make the best of it": "please make the best of it", "Please note, the blacklist will make the user unable to make a ticket/reply again": "Please note, the blacklist will make the user unable to make a ticket/reply again", @@ -489,7 +484,6 @@ "Please select software ...": "Please select software ...", "---": "---", "Specification ": "Specification ", - "Node": "Node", "Resource Data:": "Resource Data:", "vCores": "vCores", "MB": "MB", @@ -554,9 +548,7 @@ "Total discount": "Total discount", "Taxable amount": "Taxable amount", "Tax rate": "Tax rate", - "Total taxes": "Total taxes", "Shipping": "Shipping", - "Total amount": "Total amount", "Notes": "Notes", "Amount in words": "Amount in words", "Please pay until": "Please pay until", @@ -593,11 +585,66 @@ "Billing Period": "Billing Period", "Next Billing Cycle": "Next Billing Cycle", "Manage Server": "Manage Server", - "Delete Server": "Delete Server", "Cancel Server": "Cancel Server", "Yes, cancel it!": "Yes, cancel it!", "No, abort!": "No, abort!", "Billing period": "Billing period", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", - "Caution": "Caution" + "Caution": "Caution", + "Only one of the two code inputs must be provided.": "Only one of the two code inputs must be provided.", + "At least one of the two code inputs must be provided.": "At least one of the two code inputs must be provided.", + "The coupon's was registered successfully.": "The coupon's was registered successfully.", + "coupon has been updated!": "coupon has been updated!", + "coupon has been removed!": "coupon has been removed!", + "Never": "Never", + "Legal pages updated": "Legal pages updated", + "Partner already exists": "Partner already exists", + "partner has been created!": "partner has been created!", + "partner has been updated!": "partner has been updated!", + "partner has been removed!": "partner has been removed!", + "Unknown user": "Unknown user", + "Default": "Default", + "Oops, something went wrong! Please try again later.": "Oops, something went wrong! Please try again later.", + "enabled": "enabled", + "disabled": "disabled", + "Role saved": "Role saved", + "Role updated. Name and Permissions of this Role cannot be changed": "Role updated. Name and Permissions of this Role cannot be changed", + "Role updated. Name of this Role cannot be changed": "Role updated. Name of this Role cannot be changed", + "Role removed": "Role removed", + "Server cancelled": "Server cancelled", + "An exception has occurred while trying to cancel the server\"": "An exception has occurred while trying to cancel the server\"", + "renamed": "renamed", + "servers": "servers", + "deleted": "deleted", + "old servers": "old servers", + "Category created": "Category created", + "Category name updated": "Category name updated", + "Category removed": "Category removed", + "Ticket not found on the server. It potentially got deleted earlier": "Ticket not found on the server. It potentially got deleted earlier", + "A ticket has been reopened, ID: #": "A ticket has been reopened, ID: #", + "User not found on the server. Check on the admin database or try again later.": "User not found on the server. Check on the admin database or try again later.", + "Reopen": "Reopen", + "User not found on the server. Check the admin database or try again later.": "User not found on the server. Check the admin database or try again later.", + "You can not delete the last admin!": "You can not delete the last admin!", + "User does not have the right permissions.": "User does not have the right permissions.", + "Account permanently deleted!": "Account permanently deleted!", + "The system administrator has blocked the creation of new servers.": "The system administrator has blocked the creation of new servers.", + "An exception has occurred while trying to remove a resource\"": "An exception has occurred while trying to remove a resource\"", + "This is not your Server!": "This is not your Server!", + "The system was unable to update your server product. Please try again later or contact support.": "The system was unable to update your server product. Please try again later or contact support.", + "This coupon does not exist.": "This coupon does not exist.", + "This coupon has reached the maximum amount of uses.": "This coupon has reached the maximum amount of uses.", + "This coupon has expired.": "This coupon has expired.", + "You have reached the maximum uses of this coupon.": "You have reached the maximum uses of this coupon.", + "The coupon you are trying to use would give you 100% off, so it cannot be used for this product, sorry.": "The coupon you are trying to use would give you 100% off, so it cannot be used for this product, sorry.", + "Select": "Select", + "Selected": "Selected", + "Pricing": "Pricing", + "Product details": "Product details", + "Total Amount": "Total Amount", + "Partner Discount": "Partner Discount", + "Coupon Discount": "Coupon Discount", + "Enter your coupon here...": "Enter your coupon here...", + "Coupon": "Coupon", + "Checkout details": "Checkout details" } diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index b6a002940..8c7697806 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -180,8 +180,9 @@ class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $ <div class="d-flex justify-content-between"> <span class="text-muted d-inline-block">{{ __('Partner Discount') }} ({{ $discountpercent }}%)</span> - <span - class="text-muted d-inline-block">-{{ $product->formatToCurrency($discountvalue) }}</span> + <span class="text-muted d-inline-block"> + - {{ $product->formatToCurrency($discountvalue) }} + </span> </div> @endif <hr class="text-white border-secondary"> From 21e900040a1ab0be8c36385a16fb9a0d5d6006c8 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Tue, 6 Jun 2023 17:08:32 +0200 Subject: [PATCH 208/514] fix: remove unused "action area" --- themes/default/views/servers/index.blade.php | 22 +------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index f7c2cec5b..a1b827f65 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -52,27 +52,7 @@ class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span> style="max-width: 350px"> <div class="card-header"> <div class="d-flex justify-content-between align-items-center"> - <h5 class="card-title mt-1">{{ $server->name }} - </h5> - <div class="card-tools mt-1"> - <div class="dropdown no-arrow"> - <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" - aria-haspopup="true" aria-expanded="false"> - <i class="fas fa-ellipsis-v fa-sm fa-fw text-white-50"></i> - </a> - <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" - aria-labelledby="dropdownMenuLink"> - @if (!empty($phpmyadmin_url))) - <a href="{{ $phpmyadmin_url }}" - class="dropdown-item text-info" target="__blank"><i title="manage" - class="fas fa-database mr-2"></i><span>{{ __('Database') }}</span></a> - @endif - <div class="dropdown-divider"></div> - <span class="dropdown-item"><i title="Created at" - class="fas fa-sync-alt mr-2"></i><span>{{ $server->created_at->isoFormat('LL') }}</span></span> - </div> - </div> - </div> + <h5 class="card-title mt-1">{{ $server->name }}</h5> </div> </div> <div class="card-body"> From eae524b653018f91515d442afd80e4fb8f2c686f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Tue, 6 Jun 2023 17:08:54 +0200 Subject: [PATCH 209/514] =?UTF-8?q?chore:=20=F0=9F=8C=90=20Change=20all=20?= =?UTF-8?q?language=20labels=20to=20english=20lables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lang/bg.json | 25 +++++++++++++------------ lang/bs.json | 3 +++ lang/cs.json | 29 +++++++++++++++-------------- lang/de.json | 29 +++++++++++++++-------------- lang/en.json | 7 ++++++- lang/es.json | 27 ++++++++++++++------------- lang/fr.json | 3 ++- lang/he.json | 29 +++++++++++++++-------------- lang/hi.json | 27 ++++++++++++++------------- lang/hu.json | 27 ++++++++++++++------------- lang/it.json | 25 +++++++++++++------------ lang/nl.json | 27 ++++++++++++++------------- lang/pl.json | 23 ++++++++++++----------- lang/pt.json | 27 ++++++++++++++------------- lang/ro.json | 3 ++- lang/ru.json | 5 +++-- lang/sh.json | 5 ++++- lang/sk.json | 3 ++- lang/sr.json | 3 ++- lang/sv.json | 3 ++- lang/tr.json | 29 +++++++++++++++-------------- lang/zh.json | 29 +++++++++++++++-------------- 22 files changed, 209 insertions(+), 179 deletions(-) diff --git a/lang/bg.json b/lang/bg.json index bc1ab41ac..1e869cbfe 100644 --- a/lang/bg.json +++ b/lang/bg.json @@ -447,20 +447,21 @@ "Notes": "Бележки", "Amount in words": "Сума с думи", "Please pay until": "Моля, платете до", - "cs": "Чешки", - "de": "Немски", - "en": "Английски", - "es": "Испански", - "fr": "Френски", - "hi": "Хинди", - "it": "Италиански", - "nl": "Холандски", - "pl": "Полски", - "zh": "Китайски", - "tr": "Турски", - "ru": "Руски", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", + "hi": "Hindi", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", + "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Актуализирането / надграждането на сървъра ви ще нулира вашата билнгова цикъл до сега. Вашите превишени кредити ще бъдат възстановени. Цената за новия билнгов цикъл ще бъде извлечена", "Caution": "Внимание" } diff --git a/lang/bs.json b/lang/bs.json index cb014f873..5e168a9db 100644 --- a/lang/bs.json +++ b/lang/bs.json @@ -442,6 +442,9 @@ "zh": "Chinese", "tr": "Turkish", "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", "Caution": "Caution" } diff --git a/lang/cs.json b/lang/cs.json index 28574c415..1e98d37e0 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -447,20 +447,21 @@ "Notes": "Poznámky", "Amount in words": "Celkem slovy", "Please pay until": "Splatné do", - "cs": "Čeština", - "de": "Němčina", - "en": "Angličtina", - "es": "Španělština", - "fr": "Francouzština", - "hi": "Hindština", - "it": "Italština", - "nl": "Holandština", - "pl": "Polština", - "zh": "Čínština", - "tr": "Turečtina", - "ru": "Ruština", - "sv": "Švédština", - "sk": "Slovensky", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", + "hi": "Hindi", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian", "The system was unable to update your server product. Please try again later or contact support.": "Systém nebyl schopen změnit Váš balíček serveru. Prosím zkuste to znovu nebo kontaktujte podporu.", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizace/snížení vašeho serveru resetuje váš fakturační cyklus na aktuální. Vaše přeplacené kredity budou vráceny. Cena za nový fakturační cyklus bude odečtena", "Caution": "Upozornění" diff --git a/lang/de.json b/lang/de.json index d1adf4b27..ca6109f4d 100644 --- a/lang/de.json +++ b/lang/de.json @@ -447,20 +447,21 @@ "Notes": "Notizen", "Amount in words": "Betrag in Worten", "Please pay until": "Zahlbar bis", - "cs": "Tschechisch", - "de": "Deutsch", - "en": "Englisch", - "es": "Spanisch", - "fr": "Französisch", - "hi": "Indisch", - "it": "Italienisch", - "nl": "Niederländisch", - "pl": "Polnisch", - "zh": "Chinesisch", - "tr": "Türkisch", - "ru": "Russisch", - "sv": "Schwedisch", - "sk": "Slowakisch", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", + "hi": "Hindi", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian", "hourly": "Stündlich", "monthly": "Monatlich", "yearly": "Jährlich", diff --git a/lang/en.json b/lang/en.json index 69316d6ef..80a33afeb 100644 --- a/lang/en.json +++ b/lang/en.json @@ -646,5 +646,10 @@ "Coupon Discount": "Coupon Discount", "Enter your coupon here...": "Enter your coupon here...", "Coupon": "Coupon", - "Checkout details": "Checkout details" + "Checkout details": "Checkout details", + "per 6 Months": "per 6 Months", + "per 3 Months": "per 3 Months", + "per Year": "per Year", + "per Week": "per Week", + "per Day": "per Day" } diff --git a/lang/es.json b/lang/es.json index 818d8e132..f6fad67fb 100644 --- a/lang/es.json +++ b/lang/es.json @@ -447,20 +447,21 @@ "Notes": "Notas", "Amount in words": "Cantidad en palabras", "Please pay until": "Por favor pague hasta", - "cs": "Checo", - "de": "Alemán", - "en": "Inglés", - "es": "Español", - "fr": "Francés", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", "hi": "Hindi", - "it": "Italiano", - "nl": "Holandés", - "pl": "Polaco", - "zh": "Chino", - "tr": "Turco", - "ru": "Ruso", - "sv": "Sueco", - "sk": "Eslovaco", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Actualizar/Reducir el servidor restablecerá su ciclo de facturación a ahora. Se reembolsarán los créditos sobrepagados. El precio del nuevo ciclo de facturación se retirará", "Caution": "Cuidado" } diff --git a/lang/fr.json b/lang/fr.json index 65a6bc44c..73fadfe1a 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -462,5 +462,6 @@ "tr": "Turkish", "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/he.json b/lang/he.json index a64fde04a..a84e34195 100644 --- a/lang/he.json +++ b/lang/he.json @@ -447,20 +447,21 @@ "Notes": "הערות", "Amount in words": "כמות במילים", "Please pay until": "בבקשה שלם עד", - "cs": "צכית", - "de": "גרמנית", - "en": "אנגלית", - "es": "ספרדית", - "fr": "צרפתית", - "hi": "הודית", - "it": "אטלקית", - "nl": "הולנדית", - "pl": "פולנית", - "zh": "סִינִית", - "tr": "טורקית", - "ru": "רוסית", - "sv": "שוודית", - "sk": "סלובקית", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", + "hi": "Hindi", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "שדרוג / הורדת שרת יאפס את מחזור החיוב שלך לעכשיו. הקרדיטים ששילמת יוחזרו. המחיר למחזור החיוב החדש יוחסם", "Caution": "אזהרה" } diff --git a/lang/hi.json b/lang/hi.json index 1449e92af..eaa3a469a 100644 --- a/lang/hi.json +++ b/lang/hi.json @@ -449,18 +449,19 @@ "Please pay until": "कृपया भुगतान करें", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "अपने सर्वर को अपग्रेड / डाउनग्रेड करने से आपका बिलिंग साइकिल अब तक रीसेट हो जाएगा। आपके ओवरपेड क्रेडिट वापस किया जाएगा। नए बिलिंग साइकिल के लिए की गई मूल्य निकाला जाएगा", "Caution": "सावधान", - "cs": "चेक", - "de": "जर्मन", - "en": "अंग्रेज़ी", - "es": "स्पेनिश", - "fr": "फ्रांसीसी", - "hi": "हिंदी", - "it": "इटॅलियन", - "nl": "डच", - "pl": "पोलिश", - "zh": "चीनी", - "tr": "तुर्क", - "ru": "रूसी", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", + "hi": "Hindi", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/hu.json b/lang/hu.json index 30350eacf..64ad22f2e 100644 --- a/lang/hu.json +++ b/lang/hu.json @@ -449,18 +449,19 @@ "Please pay until": "Fizetési határidő", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "A szerver frissítése / lefrissítése visszaállítja a számlázási ciklust az aktuálisra. A túlfizetett Kreditet visszatérítjük. A számlázási ciklus új ára lesz kivonva", "Caution": "Figyelem", - "cs": "Cseh", - "de": "Német", - "en": "Angol", - "es": "Spanyol", - "fr": "Francia", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", "hi": "Hindi", - "it": "Olasz", - "nl": "Holland", - "pl": "Lengyel", - "zh": "Kínai", - "tr": "Török", - "ru": "Orosz", - "sv": "Svéd", - "sk": "Szlovák" + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/it.json b/lang/it.json index ac2f1020f..a7adb0c87 100644 --- a/lang/it.json +++ b/lang/it.json @@ -449,18 +449,19 @@ "Please pay until": "Per favore paga fino", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "L’aggiornamento / riduzione del tuo server reimposterà il tuo ciclo di fatturazione a ora. I tuoi crediti in eccesso saranno rimborsati. Il prezzo per il nuovo ciclo di fatturazione sarà prelevato", "Caution": "Attenzione", - "cs": "Ceco", - "de": "Tedesco", - "en": "Inglese", - "es": "Spagnolo", - "fr": "Francese", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", "hi": "Hindi", - "it": "Italiano", - "nl": "Olandese", - "pl": "Polacco", - "zh": "Cinese", - "tr": "Turco", - "ru": "Russo", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/nl.json b/lang/nl.json index 6ff9ecf53..2ac3a457e 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -449,18 +449,19 @@ "Please pay until": "Gelieve te betalen tot", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgraden/downgraden van uw server zal uw facturatiecyclus resetten naar nu. Uw overbetalen krediet zal worden terugbetaald. De prijs voor de nieuwe facturatiecyclus zal worden afgeschreven", "Caution": "Let op", - "cs": "Tsjechisch", - "de": "Duits", - "en": "Engels", - "es": "Spaans", - "fr": "Frans", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", "hi": "Hindi", - "it": "Italiaans", - "nl": "Nederlands", - "pl": "Pools", - "zh": "Chinees", - "tr": "Turks", - "ru": "Russisch", - "sv": "Zweeds", - "sk": "Slovakish" + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/pl.json b/lang/pl.json index b1f4164dd..f519a975d 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -449,18 +449,19 @@ "Please pay until": "Zapłać w ciągu:", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizacja / degradacja twojego serwera spowoduje zresetowanie cyklu rozliczeniowego do teraz. Twoje nadpłacone kredyty zostaną zwrócone. Cena za nowy cykl rozliczeniowy zostanie pobrana", "Caution": "Uwaga", - "cs": "Czeski", - "de": "Niemiecki", - "en": "Angielski", - "es": "Hiszpański", - "fr": "Francuski", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", "hi": "Hindi", - "it": "Włoski", + "it": "Italian", "nl": "Dutch", - "pl": "Polski", - "zh": "Chiński", - "tr": "Turecki", - "ru": "Rosyjski", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/pt.json b/lang/pt.json index d31d5b9b0..59f4dc9da 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -449,18 +449,19 @@ "Please pay until": "Favor pagar até", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Atualizar / Reduzir o seu servidor irá redefinir o seu ciclo de faturação para agora. Os seus créditos pagos a mais serão reembolsados. O preço para o novo ciclo de faturação será debitado", "Caution": "Cuidado", - "cs": "Tcheco", - "de": "Alemão", - "en": "Inglês", - "es": "Espanhol", - "fr": "Francês", + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", "hi": "Hindi", - "it": "Italiano", - "nl": "Holandês", - "pl": "Polonês", - "zh": "Chinês", - "tr": "Turco", - "ru": "Russo", - "sv": "Sueco", - "sk": "Eslovaco" + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/ro.json b/lang/ro.json index ea3bdea16..79186b380 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -462,5 +462,6 @@ "tr": "Turkish", "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/ru.json b/lang/ru.json index 50e458e55..a18c39516 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -460,7 +460,8 @@ "pl": "Polish", "zh": "Chinese", "tr": "Turkish", - "ru": "Русский", + "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/sh.json b/lang/sh.json index bdf0019ba..399f29d3e 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -443,5 +443,8 @@ "pl": "Polish", "zh": "Chinese", "tr": "Turkish", - "ru": "Russian" + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/sk.json b/lang/sk.json index dc88baf2c..3a84cd4c1 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -462,5 +462,6 @@ "tr": "Turkish", "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/sr.json b/lang/sr.json index 599516313..2fbb35470 100644 --- a/lang/sr.json +++ b/lang/sr.json @@ -462,5 +462,6 @@ "tr": "Turkish", "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/sv.json b/lang/sv.json index 41d5d1c72..e53c9d875 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -462,5 +462,6 @@ "tr": "Turkish", "ru": "Russian", "sv": "Swedish", - "sk": "Slovakish" + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/tr.json b/lang/tr.json index 7d34213fc..fe1186cd1 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -449,18 +449,19 @@ "Please pay until": "Lütfen şu tarihe kadar ödeyin", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Sunucunuzu yükseltmek / düşürmek faturalandırma döngünüzü şimdiye sıfırlayacaktır. Aşırı ödenen kredileriniz iade edilecektir. Yeni faturalandırma döngüsü için ödenen tutar çekilecektir", "Caution": "Dikkat", - "cs": "Çekçe", - "de": "Almanca", - "en": "İngilizce", - "es": "İspanyolca", - "fr": "Fransızca", - "hi": "Hintçe", - "it": "İtalyanca", - "nl": "Flemenkçe", - "pl": "Polonya", - "zh": "Çince", - "tr": "Türkçe", - "ru": "Rusça", - "sv": "İsveççe", - "sk": "Slovakça" + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", + "hi": "Hindi", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian" } diff --git a/lang/zh.json b/lang/zh.json index 907ed1695..02e6ac43f 100644 --- a/lang/zh.json +++ b/lang/zh.json @@ -449,18 +449,19 @@ "Please pay until": "请支付至", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "升级/降级你的服务器将重置你的账单周期。你的多余的点数将被退还。新的账单周期的价格将被扣除", "Caution": "警告", - "cs": "捷克语", - "de": "德语", - "en": "英语", - "es": "西班牙语", - "fr": "法语", - "hi": "印度语", - "it": "意大利语", - "nl": "荷兰语", - "pl": "波兰语", - "zh": "中文(简体)", - "tr": "土耳其语", - "ru": "俄语", - "sv": "乌克兰语", - "sk": "斯洛伐克语" + "cs": "Czech", + "de": "German", + "en": "English", + "es": "Spanish", + "fr": "French", + "hi": "Hindi", + "it": "Italian", + "nl": "Dutch", + "pl": "Polish", + "zh": "Chinese", + "tr": "Turkish", + "ru": "Russian", + "sv": "Swedish", + "sk": "Slovakish", + "hu": "Hungarian" } From 61d62407175943153b01c28f2d2877b0286996b7 Mon Sep 17 00:00:00 2001 From: MrWeez <64205495+MrWeez@users.noreply.github.com> Date: Thu, 8 Jun 2023 22:41:37 +0300 Subject: [PATCH 210/514] Correction of incorrect text output --- themes/default/views/servers/index.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/views/servers/index.blade.php b/themes/default/views/servers/index.blade.php index a1b827f65..93f0baeba 100644 --- a/themes/default/views/servers/index.blade.php +++ b/themes/default/views/servers/index.blade.php @@ -257,7 +257,7 @@ class="btn btn-danger text-center float-right mr-2" const handleServerDelete = (serverId) => { Swal.fire({ title: "{{ __('Delete Server?') }}", - html: "{{!! __('This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.') !!}}", + html: "{!! __('This is an irreversible action, all files of this server will be removed. <strong>No funds will get refunded</strong>. We recommend deleting the server when server is suspended.') !!}", icon: 'warning', confirmButtonColor: '#d9534f', showCancelButton: true, From 15a8807642abbef65b4eedb0a5acc27367ea9a75 Mon Sep 17 00:00:00 2001 From: Dennis <ownerdennis8@gmail.com> Date: Thu, 8 Jun 2023 23:36:16 +0200 Subject: [PATCH 211/514] "No Server" Option --- themes/default/views/ticket/create.blade.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/themes/default/views/ticket/create.blade.php b/themes/default/views/ticket/create.blade.php index f9d35ad4d..c25b20e6b 100644 --- a/themes/default/views/ticket/create.blade.php +++ b/themes/default/views/ticket/create.blade.php @@ -44,14 +44,16 @@ </span> @endif </div> - @if ($servers->count() >= 1) + <div class="form-group col-sm-12 {{ $errors->has('server') ? ' has-error' : '' }}"> <label for="server" class="control-label">{{__("Server")}}</label> <select id="server" type="server" class="form-control" name="server"> - <option value="">{{__("Select Servers")}}</option> + <option value="">{{ __("No Server") }}</option> + @if ($servers->count() >= 1) @foreach ($servers as $server) <option value="{{ $server->id }}">{{ $server->name }}</option> @endforeach + @endif </select> @if ($errors->has('category')) @@ -60,7 +62,7 @@ </span> @endif </div> - @endif + <div class="form-group col-sm-12 {{ $errors->has('ticketcategory') ? ' has-error' : '' }}"> <label for="ticketcategory" class="control-label">{{__("Category")}}</label> <select id="ticketcategory" type="ticketcategory" class="form-control" required name="ticketcategory"> @@ -143,4 +145,3 @@ </script> @endsection - From 7795ea3d9865dc6a4f894895cd54c3c1be445c7b Mon Sep 17 00:00:00 2001 From: MrWeez <64205495+MrWeez@users.noreply.github.com> Date: Fri, 9 Jun 2023 12:17:11 +0300 Subject: [PATCH 212/514] Fixing the path of the installation log file --- public/install/forms.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/install/forms.php b/public/install/forms.php index 726e22ec3..223b26bfe 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -88,7 +88,7 @@ header('LOCATION: index.php?step=3'); } else { wh_log('Feeding the Database failed', 'debug'); - header('LOCATION: index.php?step=2.5&message=There was an error. Please check the .txt file in /var/www/controlpanel/public/install/logs !'); + header('LOCATION: index.php?step=2.5&message=There was an error. Please check the installer.log file in /var/www/controlpanel/storage/logs !'); } } From 3b18b79b475954057dbe690a5d0533a9f803ac09 Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Fri, 9 Jun 2023 13:38:56 +0300 Subject: [PATCH 213/514] Language variables --- lang/bg.json | 8 +++++++- lang/bs.json | 8 +++++++- lang/cs.json | 9 ++++++++- lang/de.json | 6 +++++- lang/en.json | 6 +++++- lang/es.json | 6 +++++- lang/fr.json | 6 +++++- lang/he.json | 6 +++++- lang/hi.json | 6 +++++- lang/hu.json | 6 +++++- lang/it.json | 6 +++++- lang/nl.json | 6 +++++- lang/pl.json | 6 +++++- lang/pt.json | 6 +++++- lang/ro.json | 6 +++++- lang/ru.json | 10 +++++++--- lang/sh.json | 6 +++++- lang/sk.json | 6 +++++- lang/sr.json | 6 +++++- lang/sv.json | 6 +++++- lang/tr.json | 6 +++++- lang/zh.json | 6 +++++- 22 files changed, 119 insertions(+), 24 deletions(-) diff --git a/lang/bg.json b/lang/bg.json index 1e869cbfe..b1fc35680 100644 --- a/lang/bg.json +++ b/lang/bg.json @@ -463,5 +463,11 @@ "sk": "Slovakish", "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Актуализирането / надграждането на сървъра ви ще нулира вашата билнгова цикъл до сега. Вашите превишени кредити ще бъдат възстановени. Цената за новия билнгов цикъл ще бъде извлечена", - "Caution": "Внимание" + "Caution": "Внимание", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Актуализирането / надграждането на сървъра ви ще нулира вашата билнгова цикъл до сега. Вашите превишени кредити ще бъдат възстановени. Цената за новия билнгов цикъл ще бъде извлечена", + "Caution": "Внимание", + "You can not see your Referral Code": "Не можете да видите вашия код за препращане", + "SERVER NAME": "ИМЕ НА СЪРВЪР", + "STORAGE": "Съхранение", + "Cancel": "Отказ" } diff --git a/lang/bs.json b/lang/bs.json index 5e168a9db..2b32fdcca 100644 --- a/lang/bs.json +++ b/lang/bs.json @@ -446,5 +446,11 @@ "sk": "Slovakish", "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", - "Caution": "Caution" + "Caution": "Caution", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", + "Caution": "Caution", + "You can not see your Referral Code": "You can not see your Referral Code", + "SERVER NAME": "SERVER NAME", + "STORAGE": "STORAGE", + "Cancel": "Cancel" } diff --git a/lang/cs.json b/lang/cs.json index 1e98d37e0..d3101515a 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -464,5 +464,12 @@ "hu": "Hungarian", "The system was unable to update your server product. Please try again later or contact support.": "Systém nebyl schopen změnit Váš balíček serveru. Prosím zkuste to znovu nebo kontaktujte podporu.", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizace/snížení vašeho serveru resetuje váš fakturační cyklus na aktuální. Vaše přeplacené kredity budou vráceny. Cena za nový fakturační cyklus bude odečtena", - "Caution": "Upozornění" + "Caution": "Upozornění", + "The system was unable to update your server product. Please try again later or contact support.": "Systém nebyl schopen změnit Váš balíček serveru. Prosím zkuste to znovu nebo kontaktujte podporu.", + "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizace/snížení vašeho serveru resetuje váš fakturační cyklus na aktuální. Vaše přeplacené kredity budou vráceny. Cena za nový fakturační cyklus bude odečtena", + "Caution": "Upozornění", + "You can not see your Referral Code": "Nemůžete vidět váš doporučující kód", + "SERVER NAME": "NÁZEV SERVERU", + "STORAGE": "ZAVAZADLO", + "Cancel": "Zrušit" } diff --git a/lang/de.json b/lang/de.json index ca6109f4d..e9ab272d9 100644 --- a/lang/de.json +++ b/lang/de.json @@ -486,5 +486,9 @@ "No, abort!": "Abbrechen", "Billing period": "Abrechnungsperiode", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Das Upgrade/Downgrade Ihres Servers wird Ihre Abrechnungsperiode auf \"jetzt\" zurücksetzen. Ihre überzahlten Credits werden erstattet. Der Preis für die neue Abrechnungsperiode wird abgebucht.", - "Caution": "Achtung" + "Caution": "Achtung", + "You can not see your Referral Code": "Sie können Ihren Empfehlungscode nicht sehen", + "SERVER NAME": "SERVERNAMEN", + "STORAGE": "STORAGE", + "Cancel": "Stornieren" } diff --git a/lang/en.json b/lang/en.json index 80a33afeb..633a4ec2c 100644 --- a/lang/en.json +++ b/lang/en.json @@ -651,5 +651,9 @@ "per 3 Months": "per 3 Months", "per Year": "per Year", "per Week": "per Week", - "per Day": "per Day" + "per Day": "per Day", + "You can not see your Referral Code": "You can not see your Referral Code", + "SERVER NAME": "SERVER NAME", + "STORAGE": "STORAGE", + "Cancel": "Cancel" } diff --git a/lang/es.json b/lang/es.json index f6fad67fb..50ecfb4a4 100644 --- a/lang/es.json +++ b/lang/es.json @@ -463,5 +463,9 @@ "sk": "Slovakish", "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Actualizar/Reducir el servidor restablecerá su ciclo de facturación a ahora. Se reembolsarán los créditos sobrepagados. El precio del nuevo ciclo de facturación se retirará", - "Caution": "Cuidado" + "Caution": "Cuidado", + "You can not see your Referral Code": "Не puedes ver tu Código de Referencia", + "SERVER NAME": "NOMBRE DEL SERVIDOR", + "STORAGE": "ALMACENAMIENTO", + "Cancel": "Cancelar" } diff --git a/lang/fr.json b/lang/fr.json index 73fadfe1a..16e7150f3 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Vous ne pouvez pas voir votre Code de parrainage", + "SERVER NAME": "NOM DU SERVEUR", + "STORAGE": "STOCKAGE", + "Cancel": "Annuler" } diff --git a/lang/he.json b/lang/he.json index a84e34195..4d09b0397 100644 --- a/lang/he.json +++ b/lang/he.json @@ -463,5 +463,9 @@ "sk": "Slovakish", "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "שדרוג / הורדת שרת יאפס את מחזור החיוב שלך לעכשיו. הקרדיטים ששילמת יוחזרו. המחיר למחזור החיוב החדש יוחסם", - "Caution": "אזהרה" + "Caution": "אזהרה", + "You can not see your Referral Code": "You can not see your Referral Code", + "SERVER NAME": "SERVER NAME", + "STORAGE": "STORAGE", + "Cancel": "Cancel" } diff --git a/lang/hi.json b/lang/hi.json index eaa3a469a..c36b7d014 100644 --- a/lang/hi.json +++ b/lang/hi.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "आप अपना रेफरल कोड नहीं देख सकते", + "SERVER NAME": "सर्वर का नाम", + "STORAGE": "स्टोरेज", + "Cancel": "रद्द करें" } diff --git a/lang/hu.json b/lang/hu.json index 64ad22f2e..af3ac9622 100644 --- a/lang/hu.json +++ b/lang/hu.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Nem látod a hivatkozási kódodat", + "SERVER NAME": "SZERVERNEVE", + "STORAGE": "TÁROLÁS", + "Cancel": "Mégse" } diff --git a/lang/it.json b/lang/it.json index a7adb0c87..1fce8de29 100644 --- a/lang/it.json +++ b/lang/it.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Non puoi vedere il tuo Codice di Riferimento", + "SERVER NAME": "NOME DEL SERVER", + "STORAGE": "ARCHIVIAZIONE", + "Cancel": "Annulla" } diff --git a/lang/nl.json b/lang/nl.json index 2ac3a457e..6db2f669d 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Je kunt je Verwijzingscode niet zien", + "SERVER NAME": "SERVERNAAM", + "STORAGE": "OPSLAG", + "Cancel": "Annuleren" } diff --git a/lang/pl.json b/lang/pl.json index f519a975d..d14b00803 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Nie możesz zobaczyć swojego kodu polecającego", + "SERVER NAME": "NAZWA SERWERA", + "STORAGE": "PAMIĘĆ", + "Cancel": "Anuluj" } diff --git a/lang/pt.json b/lang/pt.json index 59f4dc9da..9083b97d4 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Você não pode ver o seu Código de Referência", + "SERVER NAME": "NOME DO SERVIDOR", + "STORAGE": "ARMAZENAMENTO", + "Cancel": "Cancelar" } diff --git a/lang/ro.json b/lang/ro.json index 79186b380..d9a949671 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Nu poți vedea codul tău de recomandare", + "SERVER NAME": "NUMELE SERVERULUI", + "STORAGE": "STOCARE", + "Cancel": "Anulare" } diff --git a/lang/ru.json b/lang/ru.json index a18c39516..ec254e371 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -149,7 +149,6 @@ "Will hide this option from being selected": "Скрывает эту опцию от выбора", "Price in": "Цена в", "Memory": "Оперативная память", - "Cpu": "Процессор", "Swap": "Файл подкачки", "This is what the users sees": "Вот что видят пользователи", "Disk": "Диск", @@ -164,7 +163,6 @@ "This product will only be available for these nodes": "Данный продукт будет доступен только для этих узлов", "This product will only be available for these eggs": "Этот продукт будет доступен только для этих яиц", "Product": "Продукт", - "CPU": "Процессор", "Updated at": "Обновлено", "User": "Пользователь", "Config": "Конфигурация", @@ -463,5 +461,11 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Вы не можете видеть свой реферельный код", + "SERVER NAME": "НАЗВАНИЕ СЕРВЕРА", + "CPU": "ЦПУ", + "MEMORY": "ОЗУ", + "STORAGE": "ХРАНИЛИЩЕ", + "Cancel": "Отменить" } diff --git a/lang/sh.json b/lang/sh.json index 399f29d3e..2890e77e7 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -446,5 +446,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "You can not see your Referral Code", + "SERVER NAME": "SERVER NAME", + "STORAGE": "STORAGE", + "Cancel": "Cancel" } diff --git a/lang/sk.json b/lang/sk.json index 3a84cd4c1..2db59f349 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Nemôžete vidieť váš Referral Code", + "SERVER NAME": "MENO SERVERA", + "STORAGE": "ÚLOŽISKO", + "Cancel": "Zrušiť" } diff --git a/lang/sr.json b/lang/sr.json index 2fbb35470..50cab9924 100644 --- a/lang/sr.json +++ b/lang/sr.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Не можете видети свој код за препоруку", + "SERVER NAME": "ИМЕ СЕРВЕРА", + "STORAGE": "СКЛАДИШТЕ", + "Cancel": "Откажи" } diff --git a/lang/sv.json b/lang/sv.json index e53c9d875..552f0def3 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Du kan inte se din hänvisningskod", + "SERVER NAME": "SERVERNAMN", + "STORAGE": "LAGRING", + "Cancel": "Avbryt" } diff --git a/lang/tr.json b/lang/tr.json index fe1186cd1..f05ebe60e 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "Yönlendirme Kodunuzu göremezsiniz", + "SERVER NAME": "SUNUCU ADI", + "STORAGE": "DEPOLAMA", + "Cancel": "Iptal" } diff --git a/lang/zh.json b/lang/zh.json index 02e6ac43f..58e00e356 100644 --- a/lang/zh.json +++ b/lang/zh.json @@ -463,5 +463,9 @@ "ru": "Russian", "sv": "Swedish", "sk": "Slovakish", - "hu": "Hungarian" + "hu": "Hungarian", + "You can not see your Referral Code": "您无法查看您的推荐码", + "SERVER NAME": "服务器名称", + "STORAGE": "存储", + "Cancel": "取消" } From fd5790c7750a05a7b0731f4263f0d03ae3129a27 Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Fri, 9 Jun 2023 13:46:19 +0300 Subject: [PATCH 214/514] Translation of some fields --- themes/default/views/profile/index.blade.php | 5 ++--- .../default/views/servers/settings.blade.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/themes/default/views/profile/index.blade.php b/themes/default/views/profile/index.blade.php index 459384379..ba95bca44 100644 --- a/themes/default/views/profile/index.blade.php +++ b/themes/default/views/profile/index.blade.php @@ -105,14 +105,14 @@ class="fa fa-coins mr-2"></i>{{ $user->Credits() }}</span> <div class="mt-1"> <span class="badge badge-success"><i class="fa fa-user-check mr-2"></i> - {{_("Referral URL")}} : + {{__("Referral URL")}} : <span onclick="onClickCopy()" id="RefLink" style="cursor: pointer;"> {{route("register")}}?ref={{$user->referral_code}}</span> </span> @else <span class="badge badge-warning"><i class="fa fa-user-check mr-2"></i> - {{_("You can not see your Referral Code")}}</span> + {{__("You can not see your Referral Code")}}</span> @endcan </div> @endif @@ -367,4 +367,3 @@ function onClickCopy() { } </script> @endsection - diff --git a/themes/default/views/servers/settings.blade.php b/themes/default/views/servers/settings.blade.php index bbcdf72d7..7aba57d01 100644 --- a/themes/default/views/servers/settings.blade.php +++ b/themes/default/views/servers/settings.blade.php @@ -33,7 +33,7 @@ <div class="row"> <div class="col-8"> <div class="numbers"> - <p class="text-sm mb-0 text-uppercase font-weight-bold">SERVER NAME</p> + <p class="text-sm mb-0 text-uppercase font-weight-bold">{{ __('SERVER NAME') }}</p> <h5 class="font-weight-bolder" id="domain_text"> <span class="text-success text-sm font-weight-bolder">{{ $server->name }}</span> </h5> @@ -54,9 +54,9 @@ <div class="row"> <div class="col-8"> <div class="numbers"> - <p class="text-sm mb-0 text-uppercase font-weight-bold">CPU</p> + <p class="text-sm mb-0 text-uppercase font-weight-bold">{{ __('CPU') }}</p> <h5 class="font-weight-bolder"> - <span class="text-success text-sm font-weight-bolder">@if($server->product->cpu == 0)Unlimited @else {{$server->product->cpu}} % @endif</span> + <span class="text-success text-sm font-weight-bolder">@if($server->product->cpu == 0){{ __('Unlimited') }} @else {{$server->product->cpu}} % @endif</span> </h5> </div> </div> @@ -75,9 +75,9 @@ <div class="row"> <div class="col-8"> <div class="numbers"> - <p class="text-sm mb-0 text-uppercase font-weight-bold">Memory</p> + <p class="text-sm mb-0 text-uppercase font-weight-bold">{{ __('MEMORY') }}</p> <h5 class="font-weight-bolder"> - <span class="text-success text-sm font-weight-bolder">@if($server->product->memory == 0)Unlimited @else {{$server->product->memory}}MB @endif</span> + <span class="text-success text-sm font-weight-bolder">@if($server->product->memory == 0){{ __('Unlimited') }} @else {{$server->product->memory}}MB @endif</span> </h5> </div> </div> @@ -96,9 +96,9 @@ <div class="row"> <div class="col-8"> <div class="numbers"> - <p class="text-sm mb-0 text-uppercase font-weight-bold">STORAGE</p> + <p class="text-sm mb-0 text-uppercase font-weight-bold">{{ __('STORAGE') }}</p> <h5 class="font-weight-bolder"> - <span class="text-success text-sm font-weight-bolder">@if($server->product->disk == 0)Unlimited @else {{$server->product->disk}}MB @endif</span> + <span class="text-success text-sm font-weight-bolder">@if($server->product->disk == 0){{ __('Unlimited') }} @else {{$server->product->disk}}MB @endif</span> </h5> </div> </div> @@ -266,7 +266,7 @@ class="btn btn-info btn-md"> @endif @endforeach </select> - + <br> <strong>{{__("Caution") }}:</strong> {{__("Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed")}}. <br> <br> {{__("Server will be automatically restarted once upgraded")}} </div> @@ -298,7 +298,7 @@ class="btn btn-danger btn-md"> {{__("This is an irreversible action, all files of this server will be removed!")}} </div> <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> + <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ __('Cancel') }}</button> <form class="d-inline" method="post" action="{{ route('servers.destroy', ['server' => $server->id]) }}"> @csrf @method('DELETE') From 57ca71225495ce73c3bdb1361782b70fb5bc922c Mon Sep 17 00:00:00 2001 From: MrWeez <64205495+MrWeez@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:29:25 +0300 Subject: [PATCH 215/514] Removing duplicate rows --- lang/bg.json | 2 -- lang/bs.json | 3 --- lang/cs.json | 3 --- lang/sh.json | 1 - 4 files changed, 9 deletions(-) diff --git a/lang/bg.json b/lang/bg.json index b1fc35680..a5c070c57 100644 --- a/lang/bg.json +++ b/lang/bg.json @@ -464,8 +464,6 @@ "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Актуализирането / надграждането на сървъра ви ще нулира вашата билнгова цикъл до сега. Вашите превишени кредити ще бъдат възстановени. Цената за новия билнгов цикъл ще бъде извлечена", "Caution": "Внимание", - "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Актуализирането / надграждането на сървъра ви ще нулира вашата билнгова цикъл до сега. Вашите превишени кредити ще бъдат възстановени. Цената за новия билнгов цикъл ще бъде извлечена", - "Caution": "Внимание", "You can not see your Referral Code": "Не можете да видите вашия код за препращане", "SERVER NAME": "ИМЕ НА СЪРВЪР", "STORAGE": "Съхранение", diff --git a/lang/bs.json b/lang/bs.json index 2b32fdcca..fe4f110bb 100644 --- a/lang/bs.json +++ b/lang/bs.json @@ -417,7 +417,6 @@ "Value": "Value", "Edit Configuration": "Edit Configuration", "Text Field": "Text Field", - "Cancel": "Cancel", "Save": "Save", "Images and Icons may be cached, reload without cache to see your changes appear": "Images and Icons may be cached, reload without cache to see your changes appear", "Enter your companys name": "Enter your companys name", @@ -447,8 +446,6 @@ "hu": "Hungarian", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", "Caution": "Caution", - "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed", - "Caution": "Caution", "You can not see your Referral Code": "You can not see your Referral Code", "SERVER NAME": "SERVER NAME", "STORAGE": "STORAGE", diff --git a/lang/cs.json b/lang/cs.json index d3101515a..a0074d402 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -465,9 +465,6 @@ "The system was unable to update your server product. Please try again later or contact support.": "Systém nebyl schopen změnit Váš balíček serveru. Prosím zkuste to znovu nebo kontaktujte podporu.", "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizace/snížení vašeho serveru resetuje váš fakturační cyklus na aktuální. Vaše přeplacené kredity budou vráceny. Cena za nový fakturační cyklus bude odečtena", "Caution": "Upozornění", - "The system was unable to update your server product. Please try again later or contact support.": "Systém nebyl schopen změnit Váš balíček serveru. Prosím zkuste to znovu nebo kontaktujte podporu.", - "Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed": "Aktualizace/snížení vašeho serveru resetuje váš fakturační cyklus na aktuální. Vaše přeplacené kredity budou vráceny. Cena za nový fakturační cyklus bude odečtena", - "Caution": "Upozornění", "You can not see your Referral Code": "Nemůžete vidět váš doporučující kód", "SERVER NAME": "NÁZEV SERVERU", "STORAGE": "ZAVAZADLO", diff --git a/lang/sh.json b/lang/sh.json index 2890e77e7..c65182bb1 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -417,7 +417,6 @@ "Value": "Value", "Edit Configuration": "Edit Configuration", "Text Field": "Text Field", - "Cancel": "Cancel", "Save": "Save", "Images and Icons may be cached, reload without cache to see your changes appear": "Images and Icons may be cached, reload without cache to see your changes appear", "Enter your companys name": "Enter your companys name", From 458d6a3f26d947a4801a6ae6fc528795500c3c28 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 9 Jun 2023 23:13:02 +0200 Subject: [PATCH 216/514] Change design of Roles on list --- app/Http/Controllers/Admin/RoleController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index 9a521938a..b10c1b012 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -203,7 +203,7 @@ class="fa fas fa-trash"></i></button> }) ->editColumn('name', function (Role $role) { - return "<span style=\"color: $role->color\">$role->name</span>"; + return "<span style='background-color: $role->color' class='badge'>$role->name</span>"; }) ->editColumn('usercount', function ($query) { return $query->users_count; From 8785325a94e3d355632d22d5655b04f312069474 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 9 Jun 2023 23:19:12 +0200 Subject: [PATCH 217/514] fix referral not being shown on home --- themes/default/views/home.blade.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/themes/default/views/home.blade.php b/themes/default/views/home.blade.php index b3827bee1..426550823 100644 --- a/themes/default/views/home.blade.php +++ b/themes/default/views/home.blade.php @@ -251,14 +251,14 @@ class="info-box-text">{{ __('Out of Credits in', ['credits' => $general_settings <table class="table"> <thead> <tr> - @if(in_array($referral_settings->mode, ["Commission","Both"]))<th>{{ __('Reward per registered user') }}</th> @endif - @if(in_array($referral_settings->mode, ["Sign-Up","Both"]))<th>{{ __('New user payment commision') }}</th> @endif + @if(in_array($referral_settings->mode, ["comission","both"]))<th>{{ __('Reward per registered user') }}</th> @endif + @if(in_array($referral_settings->mode, ["sign-up","both"]))<th>{{ __('New user payment commision') }}</th> @endif </tr> </thead> <tbody> <tr> - @if(in_array($referral_settings->mode, ["Commission","Both"]))<td>{{ $referral_settings->reward }} {{ $general_settings->credits_display_name }}</td> @endif - @if(in_array($referral_settings->mode, ["Sign-Up","Both"]))<td>{{ $referral_settings->percentage }}%</td> @endif + @if(in_array($referral_settings->mode, ["comission","both"]))<td>{{ $referral_settings->reward }} {{ $general_settings->credits_display_name }}</td> @endif + @if(in_array($referral_settings->mode, ["sign-up","both"]))<td>{{ $referral_settings->percentage }}%</td> @endif </tr> </tbody> </table> From 3a348ee894048164bc8c03e104fb446aada6c702 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 9 Jun 2023 23:21:40 +0200 Subject: [PATCH 218/514] fix installer showing php8.2 as red --- public/install/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/install/functions.php b/public/install/functions.php index ebba003b1..8eb1318cb 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -19,7 +19,7 @@ $requirements = [ 'minPhp' => '8.1', - 'maxPhp' => '8.2', // This version is not supported + 'maxPhp' => '8.3', // This version is not supported 'mysql' => '5.7.22', ]; From 9a04f373f0f5a6dc85294bfaa6a0328a7fc0da17 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 9 Jun 2023 23:23:23 +0200 Subject: [PATCH 219/514] fix voucher showing expiry "never" --- app/Http/Controllers/Admin/VoucherController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/VoucherController.php b/app/Http/Controllers/Admin/VoucherController.php index f39f9c335..ec6c5232f 100644 --- a/app/Http/Controllers/Admin/VoucherController.php +++ b/app/Http/Controllers/Admin/VoucherController.php @@ -245,10 +245,10 @@ public function dataTable() }) ->editColumn('expires_at', function (Voucher $voucher) { if (! $voucher->expires_at) { - return ''; + return __("Never"); } - return $voucher->expires_at ? $voucher->expires_at->diffForHumans() : ''; + return $voucher->expires_at ? $voucher->expires_at->diffForHumans() : __("Never"); }) ->editColumn('code', function (Voucher $voucher) { return "<code>{$voucher->code}</code>"; From dfd5c184af6433ff4b78c64c7422f167b224bf69 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 9 Jun 2023 23:25:58 +0200 Subject: [PATCH 220/514] increase max width to show lonjg emails on user show --- themes/default/views/admin/users/show.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/views/admin/users/show.blade.php b/themes/default/views/admin/users/show.blade.php index 5656c307b..6bdc21921 100644 --- a/themes/default/views/admin/users/show.blade.php +++ b/themes/default/views/admin/users/show.blade.php @@ -100,7 +100,7 @@ <label>{{ __('Email') }}</label> </div> <div class="col-lg-8"> - <span style="max-width: 250px;" class="d-inline-block text-truncate"> + <span style="max-width: 400px;" class="d-inline-block text-truncate"> {{ $user->email }} </span> </div> From 77e5b1302ed728425f5d47be849fdbc08b526e20 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 9 Jun 2023 23:29:29 +0200 Subject: [PATCH 221/514] move oom killer --- themes/default/views/admin/products/index.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/themes/default/views/admin/products/index.blade.php b/themes/default/views/admin/products/index.blade.php index 17cbf9494..a951a46f6 100644 --- a/themes/default/views/admin/products/index.blade.php +++ b/themes/default/views/admin/products/index.blade.php @@ -51,10 +51,10 @@ class="fas fa-plus mr-1"></i>{{__('Create new')}}</a> <th>{{__('Disk')}}</th> <th>{{__('Databases')}}</th> <th>{{__('Backups')}}</th> + <th>{{__('OOM Killer')}}</th> <th>{{__('Nodes')}}</th> <th>{{__('Eggs')}}</th> <th>{{__('Min Credits')}}</th> - <th>{{__('OOM Killer')}}</th> <th>{{__('Servers')}}</th> <th>{{__('Created at')}}</th> <th>{{ __('Actions') }}</th> @@ -101,10 +101,10 @@ function submitResult() { {data: "disk"}, {data: "databases"}, {data: "backups"}, + {data: "oom_killer"}, {data: "nodes", sortable: false}, {data: "eggs", sortable: false}, {data: "minimum_credits"}, - {data: "oom_killer"}, {data: "servers", sortable: false}, {data: "created_at"}, {data: "actions", sortable: false} From 31bbc4f9063f6c59745ee0a17c247b9d27860c39 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 9 Jun 2023 23:43:06 +0200 Subject: [PATCH 222/514] fix wrong credentials message on username --- themes/default/views/auth/login.blade.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/themes/default/views/auth/login.blade.php b/themes/default/views/auth/login.blade.php index 8c3cbb786..c570550d9 100644 --- a/themes/default/views/auth/login.blade.php +++ b/themes/default/views/auth/login.blade.php @@ -32,7 +32,7 @@ class="mr-1">{{ config('app.name', 'Laravel') }}</b></a> <div class="form-group"> <div class="input-group mb-3"> <input type="text" name="email" - class="form-control @error('email') is-invalid @enderror" + class="form-control @error('email') is-invalid @enderror @error('name') is-invalid @enderror" placeholder="{{ __('Email or Username') }}"> <div class="input-group-append"> <div class="input-group-text"> @@ -41,11 +41,11 @@ class="form-control @error('email') is-invalid @enderror" </div> </div> - @error('email') + @if ($errors->get("email") || $errors->get("name")) <span class="text-danger" role="alert"> - <small><strong>{{ $message }}</strong></small> + <small><strong>{{ $errors->first('email') ? $errors->first('email') : $errors->first('name') }}</strong></small> </span> - @enderror + @endif </div> <div class="form-group"> From 74c7fc9da10674b840865081737c97bea9ffecdb Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 9 Jun 2023 23:55:17 +0200 Subject: [PATCH 223/514] more details on admin overview --- .../Controllers/Admin/OverViewController.php | 4 +++- .../default/views/admin/overview/index.blade.php | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/OverViewController.php b/app/Http/Controllers/Admin/OverViewController.php index 866d8ece3..2b0a4f69d 100644 --- a/app/Http/Controllers/Admin/OverViewController.php +++ b/app/Http/Controllers/Admin/OverViewController.php @@ -37,7 +37,9 @@ public function index(GeneralSettings $general_settings) //Get counters $counters = collect(); //Set basic variables in the collection - $counters->put('users', User::query()->count()); + $counters->put('users', collect()); + $counters['users']->active = User::where("suspended", 0)->count(); + $counters['users']->total = User::query()->count(); $counters->put('credits', number_format(User::query()->whereHas("roles", function($q){ $q->where("id", "!=", "1"); })->sum('credits'), 2, '.', '')); $counters->put('payments', Payment::query()->count()); $counters->put('eggs', Egg::query()->count()); diff --git a/themes/default/views/admin/overview/index.blade.php b/themes/default/views/admin/overview/index.blade.php index 6b5bd4112..5a6fc1d53 100644 --- a/themes/default/views/admin/overview/index.blade.php +++ b/themes/default/views/admin/overview/index.blade.php @@ -58,7 +58,12 @@ class="fas fa-money-bill mr-2"></i> {{__('Support CtrlPanel')}}</a> <span class="info-box-icon bg-info elevation-1"><i class="fas fa-server"></i></span> <div class="info-box-content"> - <span class="info-box-text">{{__('Servers')}}</span> + <span class="info-box-text">{{__('Servers')}} + <i class="fas fa-info-circle mr-4" data-toggle="popover" + data-trigger="hover" data-placement="top" + data-html="true" + data-content="{{ __("This shows the total active servers and the total servers. Total active servers are all servers which are not suspended") }}"></i> + </span> <span class="info-box-number">{{$counters['servers']->active}}/{{$counters['servers']->total}}</span> </div> <!-- /.info-box-content --> @@ -71,8 +76,13 @@ class="fas fa-money-bill mr-2"></i> {{__('Support CtrlPanel')}}</a> <span class="info-box-icon bg-primary elevation-1"><i class="fas fa-users"></i></span> <div class="info-box-content"> - <span class="info-box-text">{{__('Users')}}</span> - <span class="info-box-number">{{$counters['users']}}</span> + <span class="info-box-text">{{__('Users')}} + <i class="fas fa-info-circle mr-4" data-toggle="popover" + data-trigger="hover" data-placement="top" + data-html="true" + data-content="{{ __("This shows the total active Users and the total Users. Total active Users are all Users which are not suspended") }}"></i> + </span> + <span class="info-box-number">{{$counters['users']->active}}/{{$counters['users']->total}}</span> </div> <!-- /.info-box-content --> </div> From 0b1670ad50c6976222cd2b33ade842d76a8f4159 Mon Sep 17 00:00:00 2001 From: Dennis <ownerdennis8@gmail.com> Date: Sat, 10 Jun 2023 01:24:16 +0200 Subject: [PATCH 224/514] Fix payment_method --- themes/default/views/store/checkout.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index 8c7697806..b0f91f0de 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -52,7 +52,7 @@ class="form-check-label h5 checkout-gateway-label" <span class="mr-3">{{ $gateway->name }}</span> </label> <button class="btn btn-primary rounded" type="button" - name="payment-method" id="{{ $gateway->name }}" + name="payment_method" id="{{ $gateway->name }}" value="{{ $gateway->name }}" :class="payment_method === '{{ $gateway->name }}' ? 'active' : ''" From d5e9f3cdaf22c9ff2c97c68e9456912b4d1b02b7 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sat, 10 Jun 2023 02:03:37 +0200 Subject: [PATCH 225/514] =?UTF-8?q?chore:=20=F0=9F=94=A5=20Remove=20unused?= =?UTF-8?q?=20extension=20routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Extensions/PaymentGateways/Mollie/web_routes.php | 4 ---- app/Extensions/PaymentGateways/PayPal/web_routes.php | 4 ---- app/Extensions/PaymentGateways/Stripe/web_routes.php | 4 ---- 3 files changed, 12 deletions(-) diff --git a/app/Extensions/PaymentGateways/Mollie/web_routes.php b/app/Extensions/PaymentGateways/Mollie/web_routes.php index 2314774af..6d07f533d 100644 --- a/app/Extensions/PaymentGateways/Mollie/web_routes.php +++ b/app/Extensions/PaymentGateways/Mollie/web_routes.php @@ -4,10 +4,6 @@ use App\Extensions\PaymentGateways\Mollie\MollieExtension; Route::middleware(['web', 'auth'])->group(function () { - Route::get('payment/MolliePay/{shopProduct}', function (MollieExtension $mollieExtension) { - $mollieExtension->pay(request()); - })->name('payment.MolliePay'); - Route::get( 'payment/MollieSuccess', function () { diff --git a/app/Extensions/PaymentGateways/PayPal/web_routes.php b/app/Extensions/PaymentGateways/PayPal/web_routes.php index 17d1ed028..75b1da191 100644 --- a/app/Extensions/PaymentGateways/PayPal/web_routes.php +++ b/app/Extensions/PaymentGateways/PayPal/web_routes.php @@ -4,10 +4,6 @@ use App\Extensions\PaymentGateways\PayPal\PayPalExtension; Route::middleware(['web', 'auth'])->group(function () { - Route::get('payment/PayPalPay/{shopProduct}', function (PayPalExtension $payPalExtension) { - $payPalExtension->PaypalPay(request()); - })->name('payment.PayPalPay'); - Route::get( 'payment/PayPalSuccess', function () { diff --git a/app/Extensions/PaymentGateways/Stripe/web_routes.php b/app/Extensions/PaymentGateways/Stripe/web_routes.php index c5640aba5..742940d8c 100644 --- a/app/Extensions/PaymentGateways/Stripe/web_routes.php +++ b/app/Extensions/PaymentGateways/Stripe/web_routes.php @@ -4,10 +4,6 @@ use App\Extensions\PaymentGateways\Stripe\StripeExtension; Route::middleware(['web', 'auth'])->group(function () { - Route::get('payment/StripePay/{shopProduct}', function (StripeExtension $stripeExtension) { - $stripeExtension->StripePay(request()); - })->name('payment.StripePay'); - Route::get( 'payment/StripeSuccess', function () { From 2ca38e5906d6d559e59d2dc5afc0cb8bf76d546e Mon Sep 17 00:00:00 2001 From: MrWeez <64205495+MrWeez@users.noreply.github.com> Date: Sat, 10 Jun 2023 18:07:52 +0300 Subject: [PATCH 226/514] Incorrect data output --- themes/default/views/home.blade.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/themes/default/views/home.blade.php b/themes/default/views/home.blade.php index 426550823..f6bd50047 100644 --- a/themes/default/views/home.blade.php +++ b/themes/default/views/home.blade.php @@ -249,16 +249,16 @@ class="info-box-text">{{ __('Out of Credits in', ['credits' => $general_settings <hr style="width: 100%; height:1px; border-width:0; background-color:#6c757d; margin-bottom: 0px"> <table class="table"> - <thead> + <thead> <tr> - @if(in_array($referral_settings->mode, ["comission","both"]))<th>{{ __('Reward per registered user') }}</th> @endif - @if(in_array($referral_settings->mode, ["sign-up","both"]))<th>{{ __('New user payment commision') }}</th> @endif + @if(in_array($referral_settings->mode, ["sign-up","both"]))<th>{{ __('Reward per registered user') }}</th> @endif + @if(in_array($referral_settings->mode, ["commission","both"]))<th>{{ __('New user payment commision') }}</th> @endif </tr> </thead> <tbody> <tr> - @if(in_array($referral_settings->mode, ["comission","both"]))<td>{{ $referral_settings->reward }} {{ $general_settings->credits_display_name }}</td> @endif - @if(in_array($referral_settings->mode, ["sign-up","both"]))<td>{{ $referral_settings->percentage }}%</td> @endif + @if(in_array($referral_settings->mode, ["sign-up","both"]))<td>{{ $referral_settings->reward }} {{ $general_settings->credits_display_name }}</td> @endif + @if(in_array($referral_settings->mode, ["commission","both"]))<td>{{ $referral_settings->percentage }}%</td> @endif </tr> </tbody> </table> From 8fe6ad0d5278a2f9bd948c59347c31413f57158f Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 25 Jun 2023 14:30:03 +0200 Subject: [PATCH 227/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Description=20of?= =?UTF-8?q?=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Settings/ServerSettings.php | 6 +++--- app/Settings/UserSettings.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Settings/ServerSettings.php b/app/Settings/ServerSettings.php index 3043066ad..3a82ac3bc 100644 --- a/app/Settings/ServerSettings.php +++ b/app/Settings/ServerSettings.php @@ -45,12 +45,12 @@ public static function getOptionInputData() 'creation_enabled' => [ 'label' => 'Creation Enabled', 'type' => 'boolean', - 'description' => 'Whether or not users can create servers.', + 'description' => 'Enable the user server creation.', ], 'enable_upgrade' => [ - 'label' => 'Enable Upgrade', + 'label' => 'Server Upgrade Enabled', 'type' => 'boolean', - 'description' => 'Whether or not users can upgrade their servers.', + 'description' => 'Enable the server upgrade feature.', ], ]; } diff --git a/app/Settings/UserSettings.php b/app/Settings/UserSettings.php index df85053eb..1260a94e1 100644 --- a/app/Settings/UserSettings.php +++ b/app/Settings/UserSettings.php @@ -106,14 +106,14 @@ public static function getOptionInputData() 'description' => 'The amount of servers a user can create after they verify their email.', ], 'register_ip_check' => [ - 'label' => 'Register IP Check', + 'label' => 'Register IP Check Enabled', 'type' => 'boolean', 'description' => 'Check if the IP a user is registering from is already in use.', ], 'creation_enabled' => [ 'label' => 'Creation Enabled', 'type' => 'boolean', - 'description' => 'Whether or not users can create servers.', + 'description' => 'Enable the user registration.', ], ]; } From c4b75bf760a524496cc470ffa783d55904562d9c Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Sun, 25 Jun 2023 15:10:47 +0200 Subject: [PATCH 228/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Settings=20descri?= =?UTF-8?q?ption?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Settings/UserSettings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Settings/UserSettings.php b/app/Settings/UserSettings.php index 1260a94e1..adc849af3 100644 --- a/app/Settings/UserSettings.php +++ b/app/Settings/UserSettings.php @@ -91,9 +91,9 @@ public static function getOptionInputData() 'description' => 'The minimum amount of credits a user needs to create a server.', ], 'server_limit_after_irl_purchase' => [ - 'label' => 'Server Limit After IRL Purchase', + 'label' => 'Server Limit After first purchase', 'type' => 'number', - 'description' => 'The amount of servers a user can create after they purchase a server.', + 'description' => 'The amount of servers a user can create after they make their first purchase.', ], 'server_limit_after_verify_discord' => [ 'label' => 'Server Limit After Verify Discord', From df0b9f57a44c86c0e21badcdbb0529eedc82e95c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 31 Jul 2023 14:57:04 +0200 Subject: [PATCH 229/514] add favicon on preview --- storage/app/public/logo.png | Bin 0 -> 95961 bytes .../views/admin/settings/index.blade.php | 3 +++ themes/default/views/layouts/app.blade.php | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 storage/app/public/logo.png diff --git a/storage/app/public/logo.png b/storage/app/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1dda9e7c6c5aa69f3b2d512e6a0521aee51f7b3c GIT binary patch literal 95961 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�KPoOiO1a2iqo1Ahy1@`)$rGo5#F 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 Ef&*k|!vFvP literal 0 HcmV?d00001 diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index dce0bbb30..78454fd7c 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -120,6 +120,9 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> <div class="row"> <div class="card ml-5" style="width: 18rem;"> <span class="h3 text-center">{{ __('FavIcon') }} </span> + <img src="{{ Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('images/controlpanel_logo.png') }}" + style="width:5vw;display: block; margin-left: auto;margin-right: auto" + class="card-img-top" alt="..."> <div class="card-body"> </div> diff --git a/themes/default/views/layouts/app.blade.php b/themes/default/views/layouts/app.blade.php index 2c4502a3f..240eb2466 100644 --- a/themes/default/views/layouts/app.blade.php +++ b/themes/default/views/layouts/app.blade.php @@ -7,14 +7,14 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <meta content="{{ $website_settings->seo_title }}" property="og:title"> <meta content="{{ $website_settings->seo_description }}" property="og:description"> - <meta content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}' property="og:image"> + <meta content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('/logo.png') : asset('images/controlpanel_logo.png') }}' property="og:image"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <link rel="icon" - href="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('favicon.ico') ? \Illuminate\Support\Facades\Storage::disk('public')->url('favicon.ico') : asset('favicon.ico') }}" + href="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('favicon.ico') }}" type="image/x-icon"> <script src="{{ asset('js/app.js') }}" defer></script> From 9ced34a51b098a5aa9abe04644dcf2256146ebc1 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 31 Jul 2023 15:01:32 +0200 Subject: [PATCH 230/514] available locales is required --- app/Settings/LocaleSettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Settings/LocaleSettings.php b/app/Settings/LocaleSettings.php index 81cf2b642..4e38cbe2f 100644 --- a/app/Settings/LocaleSettings.php +++ b/app/Settings/LocaleSettings.php @@ -24,7 +24,7 @@ public static function group(): string public static function getValidations() { return [ - 'available' => 'nullable|array', + 'available' => 'array|required', 'clients_can_change' => 'nullable|string', 'datatables' => 'nullable|string', 'default' => 'required|in:' . implode(',', config('app.available_locales')), From 4069b5fd355478c6cbba3cb7f4b58bc107bf9add Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 31 Jul 2023 15:08:44 +0200 Subject: [PATCH 231/514] 500 when servercreation is disabled --- themes/default/views/servers/create.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 5b8a3f3c3..588da5a40 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -38,7 +38,7 @@ class="row justify-content-center"> @if (!$server_creation_enabled) <div class="alert alert-warning p-2 m-2"> {{ __('The creation of new servers has been disabled for regular users, enable it again') }} - <a href="{{ route('admin.settings.system') }}">{{ __('here') }}</a>. + <a href="{{ route('admin.settings.index', "#Server") }}">{{ __('here') }}</a>. </div> @endif @if ($productCount === 0 || $nodeCount === 0 || count($nests) === 0 || count($eggs) === 0) From 90ebe1468ddc4887715dc1dbc22c10d3a2b34499 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 31 Jul 2023 15:13:26 +0200 Subject: [PATCH 232/514] fix installer mail issue --- public/install/forms.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/install/forms.php b/public/install/forms.php index 223b26bfe..98f05657d 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -141,7 +141,7 @@ ]; foreach ($values as $key => $value) { - $query = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '$value' WHERE `name` = '$key' AND `group` = mail"; + $query = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '$value' WHERE `name` = '$key' AND `group` = 'mail'"; $db->query($query); } From 8fdac0ee46ac7e854c8af895c0cb570c51d2b1e9 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Fri, 15 Sep 2023 13:57:00 -0400 Subject: [PATCH 233/514] feat: :sparkles: Add readable name for permissions --- app/Http/Controllers/Admin/UserController.php | 16 +- config/permissions_web.php | 189 +++++++++--------- ...add_readable_name_to_permissions_table.php | 32 +++ database/seeders/PermissionsSeeder.php | 17 +- .../BlueInfinity/views/layouts/main.blade.php | 50 ++--- .../default/views/admin/roles/edit.blade.php | 12 +- .../default/views/admin/roles/index.blade.php | 127 +++++++----- .../views/admin/ticket/index.blade.php | 6 +- themes/default/views/layouts/main.blade.php | 50 ++--- 9 files changed, 279 insertions(+), 220 deletions(-) create mode 100644 database/migrations/2023_09_15_142409_add_readable_name_to_permissions_table.php diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 4956e565c..13898f088 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -38,7 +38,7 @@ class UserController extends Controller const CHANGE_USERNAME_PERMISSION = "admin.users.write.username"; const CHANGE_PASSWORD_PERMISSION = "admin.users.write.password"; const CHANGE_ROLE_PERMISSION ="admin.users.write.role"; - const CHANGE_REFERAL_PERMISSION ="admin.users.write.referal"; + const CHANGE_REFERAL_PERMISSION ="admin.users.write.referral"; const CHANGE_PTERO_PERMISSION = "admin.users.write.pterodactyl"; const DELETE_PERMISSION = "admin.users.delete"; const NOTIFY_PERMISSION = "admin.users.notify"; @@ -361,10 +361,10 @@ public function dataTable(Request $request) return datatables($query) ->addColumn('avatar', function (User $user) { - return '<img width="28px" height="28px" class="rounded-circle ml-1" src="' . $user->getAvatar() . '">'; + return '<img width="28px" height="28px" class="ml-1 rounded-circle" src="' . $user->getAvatar() . '">'; }) ->addColumn('credits', function (User $user) { - return '<i class="fas fa-coins mr-2"></i> ' . $user->credits(); + return '<i class="mr-2 fas fa-coins"></i> ' . $user->credits(); }) ->addColumn('verified', function (User $user) { return $user->getVerifiedStatus(); @@ -378,10 +378,10 @@ public function dataTable(Request $request) $suspendText = $user->isSuspended() ? __('Unsuspend') : __('Suspend'); return ' - <a data-content="' . __('Login as User') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.loginas', $user->id) . '" class="btn btn-sm btn-primary mr-1"><i class="fas fa-sign-in-alt"></i></a> - <a data-content="' . __('Verify') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.verifyEmail', $user->id) . '" class="btn btn-sm btn-secondary mr-1"><i class="fas fa-envelope"></i></a> - <a data-content="' . __('Show') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.show', $user->id) . '" class="btn btn-sm text-white btn-warning mr-1"><i class="fas fa-eye"></i></a> - <a data-content="' . __('Edit') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.edit', $user->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> + <a data-content="' . __('Login as User') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.loginas', $user->id) . '" class="mr-1 btn btn-sm btn-primary"><i class="fas fa-sign-in-alt"></i></a> + <a data-content="' . __('Verify') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.verifyEmail', $user->id) . '" class="mr-1 btn btn-sm btn-secondary"><i class="fas fa-envelope"></i></a> + <a data-content="' . __('Show') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.show', $user->id) . '" class="mr-1 text-white btn btn-sm btn-warning"><i class="fas fa-eye"></i></a> + <a data-content="' . __('Edit') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.edit', $user->id) . '" class="mr-1 btn btn-sm btn-info"><i class="fas fa-pen"></i></a> <form class="d-inline" method="post" action="' . route('admin.users.togglesuspend', $user->id) . '"> ' . csrf_field() . ' <button data-content="' . $suspendText . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm ' . $suspendColor . ' text-white mr-1"><i class="far ' . $suspendIcon . '"></i></button> @@ -389,7 +389,7 @@ public function dataTable(Request $request) <form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.users.destroy', $user->id) . '"> ' . csrf_field() . ' ' . method_field('DELETE') . ' - <button data-content="' . __('Delete') . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="' . __('Delete') . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) diff --git a/config/permissions_web.php b/config/permissions_web.php index f0918375b..7d63fabdc 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -1,137 +1,138 @@ <?php return [ - '*', - /* - * Permissions for admin - */ + * Permissions for admin + */ + + 'All Permissions' => '*', - 'admin.roles.read', - 'admin.roles.create', - 'admin.roles.edit', - 'admin.roles.delete', + 'View Roles' => 'admin.roles.read', + 'Create Role' => 'admin.roles.create', + 'Edit Role' => 'admin.roles.edit', + 'Delete Role' => 'admin.roles.delete', - 'admin.ticket.read', - 'admin.tickets.write', - 'admin.tickets.get_notification', + 'View Tickets' => 'admin.ticket.read', + 'Manage Ticket' => 'admin.tickets.write', + 'Receive Ticket Notifications' => 'admin.tickets.get_notification', - 'admin.tickets.category.read', - 'admin.tickets.category.write', + 'Create Ticket Category' => 'admin.tickets.category.read', + 'Manage Ticket Category' => 'admin.tickets.category.write', - 'admin.ticket_blacklist.read', - 'admin.ticket_blacklist.write', + 'View Blacklist Tickets' => 'admin.ticket_blacklist.read', + 'Manage Blacklist Tickets' => 'admin.ticket_blacklist.write', - 'admin.overview.read', - 'admin.overview.sync', + 'View Overview' => 'admin.overview.read', + 'Overview Sync' => 'admin.overview.sync', - 'admin.api.read', - 'admin.api.write', + 'View Api Keys' => 'admin.api.read', + 'Manage Api Keys' => 'admin.api.write', - 'admin.users.read', - 'admin.users.write', - 'admin.users.suspend', - 'admin.users.write.credits', - 'admin.users.write.username', - 'admin.users.write.password', - 'admin.users.write.role', - 'admin.users.write.referal', - 'admin.users.write.pterodactyl', - 'admin.users.write.email', - 'admin.users.notify', - 'admin.users.login_as', - 'admin.users.delete', + 'View Users' => 'admin.users.read', + 'Manage Users' => 'admin.users.write', + 'Suspend Users' => 'admin.users.suspend', + 'Manage User Credits' => 'admin.users.write.credits', + 'Manage User Name' => 'admin.users.write.username', + 'Manage User Email' => 'admin.users.write.email', + 'Manage User Password' => 'admin.users.write.password', + 'Manage User Role' => 'admin.users.write.role', + 'Manage User Referral' => 'admin.users.write.referral', + 'Manage User Pterodactyl' => 'admin.users.write.pterodactyl', - 'admin.servers.read', - 'admin.servers.write', - 'admin.servers.suspend', - 'admin.servers.write.owner', - 'admin.servers.write.identifier', - 'admin.servers.delete', - 'admin.servers.bypass_creation_enabled', + 'Notify Users' => 'admin.users.notify', + 'Login As User' => 'admin.users.login_as', + 'Delete User' => 'admin.users.delete', - 'admin.products.read', - 'admin.products.create', - 'admin.products.edit', - 'admin.products.delete', + 'View Servers' => 'admin.servers.read', + 'Manage Servers' => 'admin.servers.write', + 'Suspend Server' => 'admin.servers.suspend', + 'Change Server Owner' => 'admin.servers.write.owner', + 'Manage Server Identifier' => 'admin.servers.write.identifier', + 'Create Server' => 'admin.servers.bypass_creation_enabled', + 'Delete Server' => 'admin.servers.delete', - 'admin.store.read', - 'admin.store.write', - 'admin.store.disable', + 'View Products' => 'admin.products.read', + 'Create Product' => 'admin.products.create', + 'Edit Product' => 'admin.products.edit', + 'Delete Product' => 'admin.products.delete', - 'admin.voucher.read', - 'admin.voucher.write', + 'View Store' => 'admin.store.read', + 'Manage Store' => 'admin.store.write', + 'Disable Store' => 'admin.store.disable', - 'admin.useful_links.read', - 'admin.useful_links.write', + 'View Vouchers' => 'admin.voucher.read', + 'Manage Voucher' => 'admin.voucher.write', - 'admin.legal.read', - 'admin.legal.write', + 'View Useful Links' => 'admin.useful_links.read', + 'Manage Useful Links' => 'admin.useful_links.write', - 'admin.payments.read', + 'View Legal' => 'admin.legal.read', + 'Manage Legal' => 'admin.legal.write', - 'admin.partners.read', - 'admin.partners.write', + 'View Payments' => 'admin.payments.read', - 'admin.coupons.read', - 'admin.coupons.write', + 'View Partners' => 'admin.partners.read', + 'Manage Partners' => 'admin.partners.write', - 'admin.logs.read', + 'View Coupons' => 'admin.coupons.read', + 'Manage Coupons' => 'admin.coupons.write', + + 'View Logs' => 'admin.logs.read', /* * Settings Permissions - */ - 'settings.discord.read', - 'settings.discord.write', + */ + 'View Discord Settings' => 'settings.discord.read', + 'Manage Discord Settings' => 'settings.discord.write', - 'settings.general.read', - 'settings.general.write', + 'View General Settings' => 'settings.general.read', + 'Manage General Settings' => 'settings.general.write', - 'settings.invoice.read', - 'settings.invoice.write', + 'View Invoice Settings' => 'settings.invoice.read', + 'Manage Invoice Settings' => 'settings.invoice.write', - 'settings.locale.read', - 'settings.locale.write', + 'View Locale Settings' => 'settings.locale.read', + 'Manage Locale Settings' => 'settings.locale.write', - 'settings.mail.read', - 'settings.mail.write', + 'View Mail Settings' => 'settings.mail.read', + 'Manage Mail Settings' => 'settings.mail.write', - 'settings.pterodactyl.read', - 'settings.pterodactyl.write', + 'View Pterodactyl Settings' => 'settings.pterodactyl.read', + 'Manage Pterodactyl Settings' => 'settings.pterodactyl.write', - 'settings.referral.read', - 'settings.referral.write', + 'View Referral Settings' => 'settings.referral.read', + 'Manage Referral Settings' => 'settings.referral.write', - 'settings.server.read', - 'settings.server.write', + 'View Server Settings' => 'settings.server.read', + 'Manage Server Settings' => 'settings.server.write', - 'settings.ticket.read', - 'settings.ticket.write', + 'View Ticket Settings' => 'settings.ticket.read', + 'Manage Ticket Settings' => 'settings.ticket.write', - 'settings.user.read', - 'settings.user.write', + 'View User Settings' => 'settings.user.read', + 'Manage User Settings' => 'settings.user.write', - 'settings.website.read', - 'settings.website.write', + 'View Website Settings' => 'settings.website.read', + 'Manage Website Settings' => 'settings.website.write', - 'settings.paypal.read', - 'settings.paypal.write', + 'View Paypal Settings' => 'settings.paypal.read', + 'Manage Paypal Settings' => 'settings.paypal.write', - 'settings.stripe.read', - 'settings.stripe.write', + 'View Stripe Settings' => 'settings.stripe.read', + 'Manage Stripe Settings' => 'settings.stripe.write', - 'settings.mollie.read', - 'settings.mollie.write', + 'View Mollie Settings' => 'settings.mollie.read', + 'Manage Mollie Settings' => 'settings.mollie.write', /* - * Permissions for users + * Permissions for users */ - 'user.server.create', - 'user.server.upgrade', - 'user.shop.buy', - 'user.ticket.read', - 'user.ticket.write', - 'user.referral', + 'User Create Server' => 'user.server.create', + 'User Upgrade Server' => 'user.server.upgrade', + 'User Shop Buy' => 'user.shop.buy', + 'User View Tickets' => 'user.ticket.read', + 'User Manage Ticket' => 'user.ticket.write', + 'User View Referral' => 'user.referral', ]; diff --git a/database/migrations/2023_09_15_142409_add_readable_name_to_permissions_table.php b/database/migrations/2023_09_15_142409_add_readable_name_to_permissions_table.php new file mode 100644 index 000000000..473db247e --- /dev/null +++ b/database/migrations/2023_09_15_142409_add_readable_name_to_permissions_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('permissions', function (Blueprint $table) { + $table->string('readable_name')->after('name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('permissions', function (Blueprint $table) { + $table->removeColumn('readable_name'); + }); + } +}; diff --git a/database/seeders/PermissionsSeeder.php b/database/seeders/PermissionsSeeder.php index e7738e704..cd3e8f346 100644 --- a/database/seeders/PermissionsSeeder.php +++ b/database/seeders/PermissionsSeeder.php @@ -3,7 +3,6 @@ namespace Database\Seeders; use App\Models\User; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; @@ -24,33 +23,29 @@ public function run() $users = User::all(); foreach($users as $user){ - $user->assignRole(4); + $user->assignRole(Role::findByName('user')); } $admins = User::where("role","admin")->get(); foreach($admins as $admin) { - $admin->syncRoles(1); + $admin->syncRoles(Role::findByName('Admin')); } $mods = User::where("role","moderator")->get(); foreach($mods as $mod) { - $mod->syncRoles(2); + $mod->syncRoles(Role::findByName('Support-Team')); } $clients = User::where("role","client")->get(); foreach($clients as $client) { - $client->syncRoles(3); + $client->syncRoles(Role::findByName('Client')); } - - - - } public function createPermissions() { - foreach (config('permissions_web') as $name) { - Permission::findOrCreate($name); + foreach(config('permissions_web') as $permission_name => $permission_value) { + Permission::create(['name' => $permission_value, 'readable_name' => $permission_name]); } } diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index b6549a7a0..154b9b1dc 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -56,12 +56,12 @@ class="fas fa-bars"></i></a> </li> <li class="nav-item d-none d-sm-inline-block"> <a href="{{ route('home') }}" class="nav-link"><i - class="fas fa-home mr-2"></i>{{ __('Home') }}</a> + class="mr-2 fas fa-home"></i>{{ __('Home') }}</a> </li> @if (!empty($discord_settings->invite_url)) <li class="nav-item d-none d-sm-inline-block"> <a href="{{ $discord_settings->invite_url }}" class="nav-link" target="__blank"><i - class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> + class="mr-2 fab fa-discord"></i>{{ __('Discord') }}</a> </li> @endif @@ -71,13 +71,13 @@ class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> <li class="nav-item dropdown"> <a class="nav-link" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <span class="mr-1 d-lg-inline text-gray-600"> - <small><i class="fa fa-language mr-2"></i></small>{{ __('Language') }} + <span class="mr-1 text-gray-600 d-lg-inline"> + <small><i class="mr-2 fa fa-language"></i></small>{{ __('Language') }} </span> </a> - <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" + <div class="shadow dropdown-menu dropdown-menu-right animated--grow-in" aria-labelledby="changeLocale"> - <form method="post" action="{{ route('changeLocale') }}" class="nav-item text-center"> + <form method="post" action="{{ route('changeLocale') }}" class="text-center nav-item"> @csrf @foreach (explode(',', $locale_settings->available) as $key) <button class="dropdown-item" name="inputLocale" value="{{ $key }}"> @@ -99,7 +99,7 @@ class="{{ $link->icon }}"></i> {{ $link->title }}</a> </ul> <!-- Right navbar links --> - <ul class="navbar-nav ml-auto"> + <ul class="ml-auto navbar-nav"> <!-- Notifications Dropdown Menu --> <li class="nav-item dropdown"> <a class="nav-link" data-toggle="dropdown" href="#"> @@ -117,9 +117,9 @@ class="badge badge-warning navbar-badge">{{ Auth::user()->unreadNotifications->c @foreach (Auth::user()->unreadNotifications->sortBy('created_at')->take(5) as $notification) <a href="{{ route('notifications.show', $notification->id) }}" class="dropdown-item"> <span class="d-inline-block text-truncate" style="max-width: 150px;"><i - class="fas fa-envelope mr-2"></i>{{ $notification->data['title'] }}</span> + class="mr-2 fas fa-envelope"></i>{{ $notification->data['title'] }}</span> <span - class="float-right text-muted text-sm">{{ $notification->created_at->longAbsoluteDiffForHumans() }} + class="float-right text-sm text-muted">{{ $notification->created_at->longAbsoluteDiffForHumans() }} ago</span> </a> @endforeach @@ -136,20 +136,20 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> <li class="nav-item dropdown"> <a class="nav-link" href="#" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <span class="mr-1 d-lg-inline text-gray-600"> - <small><i class="fas fa-coins mr-2"></i></small>{{ Auth::user()->credits() }} + <span class="mr-1 text-gray-600 d-lg-inline"> + <small><i class="mr-2 fas fa-coins"></i></small>{{ Auth::user()->credits() }} </span> </a> - <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" + <div class="shadow dropdown-menu dropdown-menu-right animated--grow-in" aria-labelledby="userDropdown"> <a class="dropdown-item" href="{{ route('store.index') }}"> - <i class="fas fa-coins fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-coins fa-sm fa-fw"></i> {{ __('Store') }} </a> <div class="dropdown-divider"></div> <a class="dropdown-item" data-toggle="modal" data-target="#redeemVoucherModal" href="javascript:void(0)"> - <i class="fas fa-money-check-alt fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-money-check-alt fa-sm fa-fw"></i> {{ __('Redeem code') }} </a> </div> @@ -158,27 +158,27 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> <li class="nav-item dropdown no-arrow"> <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <span class="mr-1 d-lg-inline text-gray-600 small"> + <span class="mr-1 text-gray-600 d-lg-inline small"> {{ Auth::user()->name }} - <img width="28px" height="28px" class="rounded-circle ml-1" + <img width="28px" height="28px" class="ml-1 rounded-circle" src="{{ Auth::user()->getAvatar() }}"> </span> </a> <!-- Dropdown - User Information --> - <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" + <div class="shadow dropdown-menu dropdown-menu-right animated--grow-in" aria-labelledby="userDropdown"> <a class="dropdown-item" href="{{ route('profile.index') }}"> - <i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-user fa-sm fa-fw"></i> {{ __('Profile') }} </a> {{-- <a class="dropdown-item" href="#"> --}} - {{-- <i class="fas fa-list fa-sm fa-fw mr-2 text-gray-400"></i> --}} + {{-- <i class="mr-2 text-gray-400 fas fa-list fa-sm fa-fw"></i> --}} {{-- Activity Log --}} {{-- </a> --}} @if (session()->get('previousUser')) <div class="dropdown-divider"></div> <a class="dropdown-item" href="{{ route('users.logbackin') }}"> - <i class="fas fa-sign-in-alt fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-sign-in-alt fa-sm fa-fw"></i> {{ __('Log back in') }} </a> @endif @@ -187,7 +187,7 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> @csrf <button class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal"> - <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-sign-out-alt fa-sm fa-fw"></i> {{ __('Logout') }} </button> </form> @@ -250,7 +250,7 @@ class="nav-link @if (Request::routeIs('store.*') || Request::routeIs('checkout') <li class="nav-item"> <a href="{{ route('ticket.index') }}" class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> - <i class="nav-icon fas fas fa-ticket-alt"></i> + <i class="nav-icon fas fa-ticket-alt"></i> <p>{{ __('Support Ticket') }}</p> </a> </li> @@ -357,7 +357,7 @@ class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> 'admin.users.write.username', 'admin.users.write.password', 'admin.users.write.role', - 'admin.users.write.referal', + 'admin.users.write.referral', 'admin.users.write.pterodactyl','admin.servers.read', 'admin.servers.write', 'admin.servers.suspend', @@ -379,7 +379,7 @@ class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> 'admin.users.write.username', 'admin.users.write.password', 'admin.users.write.role', - 'admin.users.write.referal', + 'admin.users.write.referral', 'admin.users.write.pterodactyl']) <li class="nav-item"> <a href="{{ route('admin.users.index') }}" @@ -510,7 +510,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <!-- @if (!Auth::user()->hasVerifiedEmail()) @if (Auth::user()->created_at->diffInHours(now(), false) > 1) - <div class="alert alert-warning p-2 m-2"> + <div class="p-2 m-2 alert alert-warning"> <h5><i class="icon fas fa-exclamation-circle"></i> {{ __('Warning!') }}</h5> {{ __('You have not yet verified your email address') }} <a class="text-primary" href="{{ route('verification.send') }}">{{ __('Click here to resend verification email') }}</a> diff --git a/themes/default/views/admin/roles/edit.blade.php b/themes/default/views/admin/roles/edit.blade.php index 91fef77df..76dfc3cdd 100644 --- a/themes/default/views/admin/roles/edit.blade.php +++ b/themes/default/views/admin/roles/edit.blade.php @@ -1,9 +1,9 @@ @extends('layouts.main') @section('content') - <div class="main py-4"> + <div class="py-4 main"> - <div class="card card-body border-0 shadow table-wrapper table-responsive"> + <div class="border-0 shadow card card-body table-wrapper table-responsive"> <h2 class="mb-4 h5">{{ isset($role) ? __('Edit role') : __('Create role') }}</h2> <form method="post" @@ -43,14 +43,14 @@ multiple> @foreach($permissions as $permission) <option @if(isset($role) && $role->permissions->contains($permission)) selected - @endif value="{{$permission->id}}">{{$permission->name}}</option> + @endif value="{{$permission->id}}">{{$permission->readable_name}}</option> @endforeach </x-input.select> </div> </div> - <div class="form-group d-flex justify-content-end mt-3"> + <div class="mt-3 form-group d-flex justify-content-end"> <button name="submit" type="submit" class="btn btn-primary">{{__('Submit')}}</button> </div> </form> @@ -61,7 +61,9 @@ <script> document.addEventListener('DOMContentLoaded', (event) => { - $('#permissions').select2(); + $('#permissions').select2({ + closeOnSelect: false + }); }) </script> @endsection diff --git a/themes/default/views/admin/roles/index.blade.php b/themes/default/views/admin/roles/index.blade.php index 213a7724d..45412bc5e 100644 --- a/themes/default/views/admin/roles/index.blade.php +++ b/themes/default/views/admin/roles/index.blade.php @@ -1,62 +1,91 @@ @extends('layouts.main') @section('content') - <div class="main py-4"> - @can('admin.roles.write') - <div class="d-flex justify-content-end my-3"> - <a href="{{route('admin.roles.create')}}" class="btn btn-primary"><i - class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> +<section class="content-header"> + <div class="container-fluid"> + <div class="mb-2 row"> + <div class="col-sm-6"> + <h1>{{__('Create role')}}</h1> </div> - @endcan - - <div class="card card-body border-0 shadow table-wrapper table-responsive"> - <h2 class="mb-4 h5">{{ __('Roles') }}</h2> + <div class="col-sm-6"> + <ol class="breadcrumb float-sm-right"> + <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> + <li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.roles.index')}}">{{__('Roles List')}}</a></li> + </ol> + </div> + </div> + </div> +</section> +<section class="content"> + <div class="container-fluid"> + <div class="card"> + <div class="card-header"> + <div class="d-flex justify-content-between"> + <h5 class="card-title"><i class="mr-2 fas fa-user-check"></i>{{__('Roles List')}}</h5> + </div> + @can('admin.roles.write') + <a href="{{route('admin.roles.create')}}" class="float-right btn btn-primary"><i class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> + @endcan + </div> <div class="card-body table-responsive"> + <div class="card-body table-responsive"> + <table id="datatable" class="table table-striped"> + <thead> + <tr> + <th>{{__("ID")}}</th> + <th>{{__("Name")}}</th> + <th>{{__("User count")}}</th> + <th>{{__("Permissions count")}}</th> + <th>{{__("Power")}}</th> + <th>{{__("Actions")}}</th> + </tr> + </thead> + <tbody> + </tbody> + </table> - <table id="datatable" class="table table-striped"> - <thead> - <tr> - <th>{{__("ID")}}</th> - <th>{{__("Name")}}</th> - <th>{{__("User count")}}</th> - <th>{{__("Permissions count")}}</th> - <th>{{__("Power")}}</th> - <th>{{__("Actions")}}</th> - </tr> - </thead> - <tbody> - </tbody> - </table> - + </div> </div> + </div> </div> -@endsection -<script> - - document.addEventListener("DOMContentLoaded", function () { - $('#datatable').DataTable({ - language: { - url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("SETTINGS::LOCALE:DATATABLES")}}.json' - }, - processing: true, - serverSide: true, //increases loading times too much? change back to "true" if it does - stateSave: true, - ajax: "{{route('admin.roles.datatable')}}", - columns: [ - {data: 'id'}, - {data: 'name'}, - {data: 'usercount'}, - {data: 'permissionscount'}, - {data: 'power'}, - {data: 'actions' , sortable : false}, - ], - fnDrawCallback: function( oSettings ) { - $('[data-toggle="popover"]').popover(); - } + @endsection + <script> + document.addEventListener("DOMContentLoaded", function() { + $('#datatable').DataTable({ + language: { + url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("SETTINGS::LOCALE:DATATABLES")}}.json' + }, + processing: true, + serverSide: true, //increases loading times too much? change back to "true" if it does + stateSave: true, + ajax: "{{route('admin.roles.datatable')}}", + columns: [{ + data: 'id' + }, + { + data: 'name' + }, + { + data: 'usercount' + }, + { + data: 'permissionscount' + }, + { + data: 'power' + }, + { + data: 'actions', + sortable: false + } + ], + fnDrawCallback: function(oSettings) { + $('[data-toggle="popover"]').popover(); + } + }); }); - }); -</script> + </script> diff --git a/themes/default/views/admin/ticket/index.blade.php b/themes/default/views/admin/ticket/index.blade.php index 5dc0bb936..17557a5f5 100644 --- a/themes/default/views/admin/ticket/index.blade.php +++ b/themes/default/views/admin/ticket/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{__('Ticket')}}</h1> </div> @@ -28,9 +28,9 @@ <div class="card-header"> <div class="d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fa-ticket-alt mr-2"></i>{{__('Ticket List')}}</h5> + <h5 class="card-title"><i class="mr-2 fas fa-ticket-alt"></i>{{__('Ticket List')}}</h5> </div> - <a href="{{route("admin.ticket.category.index")}}"><button class="btn btn-primary float-right">+ {{__("Add Category")}}</button></a> + <a href="{{route("admin.ticket.category.index")}}"><button class="float-right btn btn-primary">+ {{__("Add Category")}}</button></a> </div> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 9631ef64d..3e2b54d41 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -56,12 +56,12 @@ class="fas fa-bars"></i></a> </li> <li class="nav-item d-none d-sm-inline-block"> <a href="{{ route('home') }}" class="nav-link"><i - class="fas fa-home mr-2"></i>{{ __('Home') }}</a> + class="mr-2 fas fa-home"></i>{{ __('Home') }}</a> </li> @if (!empty($discord_settings->invite_url)) <li class="nav-item d-none d-sm-inline-block"> <a href="{{ $discord_settings->invite_url }}" class="nav-link" target="__blank"><i - class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> + class="mr-2 fab fa-discord"></i>{{ __('Discord') }}</a> </li> @endif @@ -71,13 +71,13 @@ class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> <li class="nav-item dropdown"> <a class="nav-link" href="#" id="languageDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <span class="mr-1 d-lg-inline text-gray-600"> - <small><i class="fa fa-language mr-2"></i></small>{{ __('Language') }} + <span class="mr-1 text-gray-600 d-lg-inline"> + <small><i class="mr-2 fa fa-language"></i></small>{{ __('Language') }} </span> </a> - <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" + <div class="shadow dropdown-menu dropdown-menu-right animated--grow-in" aria-labelledby="changeLocale"> - <form method="post" action="{{ route('changeLocale') }}" class="nav-item text-center"> + <form method="post" action="{{ route('changeLocale') }}" class="text-center nav-item"> @csrf @foreach (explode(',', $locale_settings->available) as $key) <button class="dropdown-item" name="inputLocale" value="{{ $key }}"> @@ -99,7 +99,7 @@ class="{{ $link->icon }}"></i> {{ $link->title }}</a> </ul> <!-- Right navbar links --> - <ul class="navbar-nav ml-auto"> + <ul class="ml-auto navbar-nav"> <!-- Notifications Dropdown Menu --> <li class="nav-item dropdown"> <a class="nav-link" data-toggle="dropdown" href="#"> @@ -117,9 +117,9 @@ class="badge badge-warning navbar-badge">{{ Auth::user()->unreadNotifications->c @foreach (Auth::user()->unreadNotifications->sortBy('created_at')->take(5) as $notification) <a href="{{ route('notifications.show', $notification->id) }}" class="dropdown-item"> <span class="d-inline-block text-truncate" style="max-width: 150px;"><i - class="fas fa-envelope mr-2"></i>{{ $notification->data['title'] }}</span> + class="mr-2 fas fa-envelope"></i>{{ $notification->data['title'] }}</span> <span - class="float-right text-muted text-sm">{{ $notification->created_at->longAbsoluteDiffForHumans() }} + class="float-right text-sm text-muted">{{ $notification->created_at->longAbsoluteDiffForHumans() }} ago</span> </a> @endforeach @@ -136,20 +136,20 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> <li class="nav-item dropdown"> <a class="nav-link" href="#" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <span class="mr-1 d-lg-inline text-gray-600"> - <small><i class="fas fa-coins mr-2"></i></small>{{ Auth::user()->credits() }} + <span class="mr-1 text-gray-600 d-lg-inline"> + <small><i class="mr-2 fas fa-coins"></i></small>{{ Auth::user()->credits() }} </span> </a> - <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" + <div class="shadow dropdown-menu dropdown-menu-right animated--grow-in" aria-labelledby="userDropdown"> <a class="dropdown-item" href="{{ route('store.index') }}"> - <i class="fas fa-coins fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-coins fa-sm fa-fw"></i> {{ __('Store') }} </a> <div class="dropdown-divider"></div> <a class="dropdown-item" data-toggle="modal" data-target="#redeemVoucherModal" href="javascript:void(0)"> - <i class="fas fa-money-check-alt fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-money-check-alt fa-sm fa-fw"></i> {{ __('Redeem code') }} </a> </div> @@ -158,27 +158,27 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> <li class="nav-item dropdown no-arrow"> <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <span class="mr-1 d-lg-inline text-gray-600 small"> + <span class="mr-1 text-gray-600 d-lg-inline small"> {{ Auth::user()->name }} - <img width="28px" height="28px" class="rounded-circle ml-1" + <img width="28px" height="28px" class="ml-1 rounded-circle" src="{{ Auth::user()->getAvatar() }}"> </span> </a> <!-- Dropdown - User Information --> - <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" + <div class="shadow dropdown-menu dropdown-menu-right animated--grow-in" aria-labelledby="userDropdown"> <a class="dropdown-item" href="{{ route('profile.index') }}"> - <i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-user fa-sm fa-fw"></i> {{ __('Profile') }} </a> {{-- <a class="dropdown-item" href="#"> --}} - {{-- <i class="fas fa-list fa-sm fa-fw mr-2 text-gray-400"></i> --}} + {{-- <i class="mr-2 text-gray-400 fas fa-list fa-sm fa-fw"></i> --}} {{-- Activity Log --}} {{-- </a> --}} @if (session()->get('previousUser')) <div class="dropdown-divider"></div> <a class="dropdown-item" href="{{ route('users.logbackin') }}"> - <i class="fas fa-sign-in-alt fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-sign-in-alt fa-sm fa-fw"></i> {{ __('Log back in') }} </a> @endif @@ -187,7 +187,7 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> @csrf <button class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal"> - <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i> + <i class="mr-2 text-gray-400 fas fa-sign-out-alt fa-sm fa-fw"></i> {{ __('Logout') }} </button> </form> @@ -250,7 +250,7 @@ class="nav-link @if (Request::routeIs('store.*') || Request::routeIs('checkout') <li class="nav-item"> <a href="{{ route('ticket.index') }}" class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> - <i class="nav-icon fas fas fa-ticket-alt"></i> + <i class="nav-icon fas fa-ticket-alt"></i> <p>{{ __('Support Ticket') }}</p> </a> </li> @@ -357,7 +357,7 @@ class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> 'admin.users.write.username', 'admin.users.write.password', 'admin.users.write.role', - 'admin.users.write.referal', + 'admin.users.write.referral', 'admin.users.write.pterodactyl','admin.servers.read', 'admin.servers.write', 'admin.servers.suspend', @@ -379,7 +379,7 @@ class="nav-link @if (Request::routeIs('admin.api.*')) active @endif"> 'admin.users.write.username', 'admin.users.write.password', 'admin.users.write.role', - 'admin.users.write.referal', + 'admin.users.write.referral', 'admin.users.write.pterodactyl']) <li class="nav-item"> <a href="{{ route('admin.users.index') }}" @@ -520,7 +520,7 @@ class="nav-link @if (Request::routeIs('admin.activitylogs.*')) active @endif"> <!-- @if (!Auth::user()->hasVerifiedEmail()) @if (Auth::user()->created_at->diffInHours(now(), false) > 1) - <div class="alert alert-warning p-2 m-2"> + <div class="p-2 m-2 alert alert-warning"> <h5><i class="icon fas fa-exclamation-circle"></i> {{ __('Warning!') }}</h5> {{ __('You have not yet verified your email address') }} <a class="text-primary" href="{{ route('verification.send') }}">{{ __('Click here to resend verification email') }}</a> From 7ecc29487e82d076e6cc1658d9e778970471133e Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sat, 16 Sep 2023 13:20:31 -0400 Subject: [PATCH 234/514] fix: :bug: Fix infinite credit exploit when checking email several times. --- .../Controllers/Auth/RegisterController.php | 9 ++-- app/Models/User.php | 6 ++- app/Providers/EventServiceProvider.php | 7 ++-- config/view.php | 2 +- themes/default/views/profile/index.blade.php | 42 +++++++++---------- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index 566d86fd5..b315f8d77 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -22,6 +22,7 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; +use Spatie\Permission\Models\Role; class RegisterController extends Controller { @@ -139,7 +140,7 @@ protected function create(array $data) ]); - $user->syncRoles(4); + $user->syncRoles(Role::findByName('User')); $response = $this->pterodactyl->application->post('/application/users', [ 'external_id' => null, @@ -151,15 +152,11 @@ protected function create(array $data) 'root_admin' => false, 'language' => 'en', ]); - + $user->update([ 'pterodactyl_id' => $response->json()['attributes']['id'], ]); - - - - if ($response->failed()) { $user->delete(); Log::error('Pterodactyl Registration Error: ' . $response->json()['errors'][0]['detail']); diff --git a/app/Models/User.php b/app/Models/User.php index 7c04a65bb..584da0151 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -66,6 +66,7 @@ class User extends Authenticatable implements MustVerifyEmail 'avatar', 'suspended', 'referral_code', + 'email_verified_reward', ]; /** @@ -88,6 +89,7 @@ class User extends Authenticatable implements MustVerifyEmail 'last_seen' => 'datetime', 'credits' => 'float', 'server_limit' => 'float', + 'email_verified_reward' => 'boolean' ]; public function __construct() @@ -280,9 +282,8 @@ public function getVerifiedStatus() public function verifyEmail() { - $this->forceFill([ - 'email_verified_at' => now(), + 'email_verified_at' => now() ])->save(); } @@ -290,6 +291,7 @@ public function reVerifyEmail() { $this->forceFill([ 'email_verified_at' => null, + 'email_verified_reward' => true ])->save(); } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 2ac9182dd..cc535566a 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -9,9 +9,10 @@ use App\Listeners\CreateInvoice; use App\Listeners\UnsuspendServers; use App\Listeners\UserPayment; -use App\Listeners\Verified; +use App\Listeners\Verified as ListenerVerified; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; +use Illuminate\Auth\Events\Verified; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use SocialiteProviders\Manager\SocialiteWasCalled; @@ -40,8 +41,8 @@ class EventServiceProvider extends ServiceProvider // ... other providers 'SocialiteProviders\\Discord\\DiscordExtendSocialite@handle', ], - 'Illuminate\Auth\Events\Verified' => [ - Verified::class, + Verified::class => [ + ListenerVerified::class, ], ]; diff --git a/config/view.php b/config/view.php index 22b8a18d3..b9b20d53d 100644 --- a/config/view.php +++ b/config/view.php @@ -14,7 +14,7 @@ */ 'paths' => [ - resource_path('views'), + base_path('themes'), ], /* diff --git a/themes/default/views/profile/index.blade.php b/themes/default/views/profile/index.blade.php index ba95bca44..14117343d 100644 --- a/themes/default/views/profile/index.blade.php +++ b/themes/default/views/profile/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{ __('Profile') }}</h1> </div> @@ -26,9 +26,9 @@ <div class="container-fluid"> <div class="row"> - <div class="col-lg-12 px-0"> - @if (!Auth::user()->hasVerifiedEmail() && strtolower($force_email_verification) == 'true') - <div class="alert alert-warning p-2 m-2"> + <div class="px-0 col-lg-12"> + @if (!Auth::user()->hasVerifiedEmail() && $force_email_verification) + <div class="p-2 m-2 alert alert-warning"> <h5><i class="icon fas fa-exclamation-circle"></i>{{ __('Required Email verification!') }} </h5> {{ __('You have not yet verified your email address') }} @@ -40,9 +40,9 @@ </div> @endif - @if (is_null(Auth::user()->discordUser) && strtolower($force_discord_verification) == 'true') + @if (is_null(Auth::user()->discordUser) && $force_discord_verification) @if (!empty($discord_client_id) && !empty($discord_client_secret)) - <div class="alert alert-warning p-2 m-2"> + <div class="p-2 m-2 alert alert-warning"> <h5> <i class="icon fas fa-exclamation-circle"></i>{{ __('Required Discord verification!') }} </h5> @@ -52,7 +52,7 @@ {{ __('Please contact support If you face any issues.') }} </div> @else - <div class="alert alert-danger p-2 m-2"> + <div class="p-2 m-2 alert alert-danger"> <h5> <i class="icon fas fa-exclamation-circle"></i>{{ __('Required Discord verification!') }} </h5> @@ -72,8 +72,8 @@ <div class="card-body"> <div class="e-profile"> <div class="row"> - <div class="col-12 col-sm-auto mb-4"> - <div class="slim rounded-circle border-secondary border text-gray-dark" + <div class="mb-4 col-12 col-sm-auto"> + <div class="border slim rounded-circle border-secondary text-gray-dark" data-label="Change your avatar" data-max-file-size="3" data-save-initial-image="true" style="width: 140px;height:140px; cursor: pointer" @@ -81,9 +81,9 @@ <img src="{{ $user->getAvatar() }}" alt="avatar"> </div> </div> - <div class="col d-flex flex-column flex-sm-row justify-content-between mb-3"> - <div class="text-center text-sm-left mb-2 mb-sm-0"> - <h4 class="pt-sm-2 pb-1 mb-0 text-nowrap">{{ $user->name }}</h4> + <div class="mb-3 col d-flex flex-column flex-sm-row justify-content-between"> + <div class="mb-2 text-center text-sm-left mb-sm-0"> + <h4 class="pb-1 mb-0 pt-sm-2 text-nowrap">{{ $user->name }}</h4> <p class="mb-0">{{ $user->email }} @if ($user->hasVerifiedEmail()) <i data-toggle="popover" data-trigger="hover" data-content="Verified" @@ -97,21 +97,21 @@ class="text-danger fas fa-exclamation-circle"></i> </p> <div class="mt-1"> <span class="badge badge-primary"><i - class="fa fa-coins mr-2"></i>{{ $user->Credits() }}</span> + class="mr-2 fa fa-coins"></i>{{ $user->Credits() }}</span> </div> @if($referral_enabled) @can("user.referral") <div class="mt-1"> <span class="badge badge-success"><i - class="fa fa-user-check mr-2"></i> + class="mr-2 fa fa-user-check"></i> {{__("Referral URL")}} : <span onclick="onClickCopy()" id="RefLink" style="cursor: pointer;"> {{route("register")}}?ref={{$user->referral_code}}</span> </span> @else <span class="badge badge-warning"><i - class="fa fa-user-check mr-2"></i> + class="mr-2 fa fa-user-check"></i> {{__("You can not see your Referral Code")}}</span> @endcan </div> @@ -138,7 +138,7 @@ class="fa fa-user-check mr-2"></i> class="active nav-link">{{ __('Settings') }}</a> </li> </ul> - <div class="tab-content pt-3"> + <div class="pt-3 tab-content"> <div class="tab-pane active"> <div class="row"> <div class="col"> @@ -189,7 +189,7 @@ class="form-control @error('email') is-invalid @enderror" </div> </div> <div class="row"> - <div class="col-12 col-sm-6 mb-3"> + <div class="mb-3 col-12 col-sm-6"> <div class="mb-3"><b>{{ __('Change Password') }}</b></div> <div class="row"> <div class="col"> @@ -242,7 +242,7 @@ class="form-control @error('new_password_confirmation') is-invalid @enderror" </div> </div> @if (!empty($discord_client_id) && !empty($discord_client_secret)) - <div class="col-12 col-sm-5 offset-sm-1 mb-3"> + <div class="mb-3 col-12 col-sm-5 offset-sm-1"> @if (is_null(Auth::user()->discordUser)) <b>{{ __('Link your discord account!') }}</b> <div class="verify-discord"> @@ -255,7 +255,7 @@ class="form-control @error('new_password_confirmation') is-invalid @enderror" </div> <a class="btn btn-light" href="{{ route('auth.redirect') }}"> - <i class="fab fa-discord mr-2"></i>{{ __('Login with Discord') }} + <i class="mr-2 fab fa-discord"></i>{{ __('Login with Discord') }} </a> @else <div class="verified-discord"> @@ -263,7 +263,7 @@ class="form-control @error('new_password_confirmation') is-invalid @enderror" <p>{{ __('You are verified!') }}</p> </div> </div> - <div class="row pl-2"> + <div class="pl-2 row"> <div class="small-box bg-dark"> <div class="d-flex justify-content-between"> <div class="p-3"> @@ -282,7 +282,7 @@ class="rounded-circle" <div class="small-box-footer"> <a href="{{ route('auth.redirect') }}"> <i - class="fab fa-discord mr-1"></i>{{ __('Re-Sync Discord') }} + class="mr-1 fab fa-discord"></i>{{ __('Re-Sync Discord') }} </a> </div> </div> From a471cb4021d9a9c919651c96333bd343bb7421f0 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sun, 17 Sep 2023 13:01:37 -0400 Subject: [PATCH 235/514] fix: :bug: fix #901 --- app/Http/Controllers/Admin/PaymentController.php | 11 ++++++++--- app/Listeners/UserPayment.php | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 850f5a93d..66292bd53 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -143,14 +143,19 @@ public function pay(Request $request) $subtotal = $shopProduct->price; // Apply Coupon - $isCouponValid = $this->isCouponValid($couponCode, $user, $shopProduct->id); - if ($isCouponValid) { - $subtotal = $this->applyCoupon($couponCode, $subtotal); + if ($couponCode) { + if ($this->isCouponValid($couponCode, $user, $shopProduct->id)) { + $subtotal = $this->applyCoupon($couponCode, $subtotal); + } } // Apply Partner Discount $subtotal = $subtotal - ($subtotal * $discount / 100); if ($subtotal <= 0) { + if ($couponCode) { + event(new CouponUsedEvent($couponCode)); + } + return $this->handleFreeProduct($shopProduct); } diff --git a/app/Listeners/UserPayment.php b/app/Listeners/UserPayment.php index 9e1066ef1..476573e19 100644 --- a/app/Listeners/UserPayment.php +++ b/app/Listeners/UserPayment.php @@ -49,7 +49,7 @@ public function handle(PaymentEvent $event) $shopProduct = $event->shopProduct; // only update user if payment is paid - if ($event->payment->status != PaymentStatus::PAID) { + if ($event->payment->status != PaymentStatus::PAID->value) { return; } From 04940f040b43cee6f9137a025fc4fa8812e577be Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sun, 17 Sep 2023 14:31:14 -0400 Subject: [PATCH 236/514] Aways check if has coupon code in the request. --- app/Http/Controllers/Admin/PaymentController.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 66292bd53..0b11597ef 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -180,7 +180,10 @@ public function pay(Request $request) $paymentGatewayExtension = ExtensionHelper::getExtensionClass($paymentGateway); $redirectUrl = $paymentGatewayExtension::getRedirectUrl($payment, $shopProduct, $totalPriceString); - event(new CouponUsedEvent($couponCode)); + + if ($couponCode) { + event(new CouponUsedEvent($couponCode)); + } } catch (Exception $e) { Log::error($e->getMessage()); return redirect()->route('store.index')->with('error', __('Oops, something went wrong! Please try again later.')); From 22413c3b30e53e390edab63f4fb684801698dc51 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sun, 17 Sep 2023 14:33:21 -0400 Subject: [PATCH 237/514] Remove unneeded function --- app/Traits/Coupon.php | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/app/Traits/Coupon.php b/app/Traits/Coupon.php index 5dd0e7e97..f1d06d27d 100644 --- a/app/Traits/Coupon.php +++ b/app/Traits/Coupon.php @@ -95,27 +95,6 @@ public function isCouponValid(string $couponCode, User $user, string $productId) return true; } - public function calcDiscount($productPrice, stdClass $data) - { - - if ($data->isValid) { - if ($data->couponType === 'percentage') { - return $productPrice - ($productPrice * $data->couponValue / 100); - } - - if ($data->couponType === 'amount') { - // There is no discount if the value of the coupon is greater than or equal to the value of the product. - if ($data->couponValue >= $productPrice) { - return $productPrice; - } - } - - return $productPrice - $data->couponValue; - } - - return $productPrice; - } - public function applyCoupon(string $couponCode, float $price) { $coupon = CouponModel::where('code', $couponCode)->first(); From fac8668fb8249f9d43f2942995d7ec90badc198d Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Tue, 19 Sep 2023 10:18:12 -0400 Subject: [PATCH 238/514] fix: :bug: Fix role search --- app/Http/Controllers/Admin/RoleController.php | 11 ++++----- app/Http/Controllers/Admin/UserController.php | 2 +- app/Http/Controllers/Api/RoleController.php | 2 +- app/Models/Permission.php | 22 ++++++++++++++++++ app/Models/Role.php | 23 +++++++++++++++++++ config/permission.php | 4 ++-- .../2023_05_05_090127_role_power.php | 6 ----- database/seeders/PermissionsSeeder.php | 11 +++++---- .../default/views/admin/roles/index.blade.php | 4 ++-- 9 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 app/Models/Permission.php create mode 100644 app/Models/Role.php diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index b10c1b012..b2c685f44 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -11,8 +11,8 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Spatie\Permission\Models\Permission; -use Spatie\Permission\Models\Role; +use App\Models\Permission; +use App\Models\Role; class RoleController extends Controller { @@ -182,8 +182,7 @@ public function destroy(Role $role) */ public function dataTable() { - $query = Role::query()->withCount(['users', 'permissions']); - + $query = Role::query()->withCount(['users', 'permissions'])->get(); return datatables($query) ->editColumn('id', function (Role $role) { @@ -205,10 +204,10 @@ class="fa fas fa-trash"></i></button> ->editColumn('name', function (Role $role) { return "<span style='background-color: $role->color' class='badge'>$role->name</span>"; }) - ->editColumn('usercount', function ($query) { + ->editColumn('users_count', function ($query) { return $query->users_count; }) - ->editColumn('permissionscount', function ($query){ + ->editColumn('permissions_count', function ($query){ return $query->permissions_count; }) ->editColumn('power', function (Role $role){ diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 13898f088..2cc535cea 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -26,7 +26,7 @@ use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use Spatie\QueryBuilder\QueryBuilder; -use Spatie\Permission\Models\Role; +use App\Models\Role; class UserController extends Controller { diff --git a/app/Http/Controllers/Api/RoleController.php b/app/Http/Controllers/Api/RoleController.php index 928473f8d..ac44c240f 100644 --- a/app/Http/Controllers/Api/RoleController.php +++ b/app/Http/Controllers/Api/RoleController.php @@ -10,7 +10,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Validation\Rule; -use Spatie\Permission\Models\Role; +use App\Models\Role; use Spatie\QueryBuilder\QueryBuilder; class RoleController extends Controller diff --git a/app/Models/Permission.php b/app/Models/Permission.php new file mode 100644 index 000000000..38ed7d427 --- /dev/null +++ b/app/Models/Permission.php @@ -0,0 +1,22 @@ +<?php + +namespace App\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Spatie\Permission\Models\Permission as BasePermission; + +class Permission extends BasePermission +{ + use HasFactory; + + /** + * The attributes that are mass assignable. + * + * @var array<int, string> + */ + protected $fillable = [ + 'name', + 'guard_name', + 'readable_name' + ]; +} diff --git a/app/Models/Role.php b/app/Models/Role.php new file mode 100644 index 000000000..a3b7e6e10 --- /dev/null +++ b/app/Models/Role.php @@ -0,0 +1,23 @@ +<?php + +namespace App\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Spatie\Permission\Models\Role as BaseRole; + +class Role extends BaseRole +{ + use HasFactory; + + /** + * The attributes that are mass assignable. + * + * @var array<int, string> + */ + protected $fillable = [ + 'name', + 'guard_name', + 'power', + 'color' + ]; +} diff --git a/config/permission.php b/config/permission.php index 5aeaab7c8..f06debdc4 100644 --- a/config/permission.php +++ b/config/permission.php @@ -13,7 +13,7 @@ * `Spatie\Permission\Contracts\Permission` contract. */ - 'permission' => Spatie\Permission\Models\Permission::class, + 'permission' => App\Models\Permission::class, /* * When using the "HasRoles" trait from this package, we need to know which @@ -24,7 +24,7 @@ * `Spatie\Permission\Contracts\Role` contract. */ - 'role' => Spatie\Permission\Models\Role::class, + 'role' => App\Models\Role::class, ], diff --git a/database/migrations/2023_05_05_090127_role_power.php b/database/migrations/2023_05_05_090127_role_power.php index 7eb0d9113..9c8da51d0 100644 --- a/database/migrations/2023_05_05_090127_role_power.php +++ b/database/migrations/2023_05_05_090127_role_power.php @@ -2,7 +2,6 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Schema; return new class extends Migration @@ -17,11 +16,6 @@ public function up() Schema::table('roles', function (Blueprint $table) { $table->integer('power')->after("color")->default(50); }); - - Artisan::call('db:seed', [ - '--class' => 'PermissionsSeeder', - '--force' => true - ]); } /** diff --git a/database/seeders/PermissionsSeeder.php b/database/seeders/PermissionsSeeder.php index cd3e8f346..5ddef8f4d 100644 --- a/database/seeders/PermissionsSeeder.php +++ b/database/seeders/PermissionsSeeder.php @@ -6,6 +6,7 @@ use Illuminate\Database\Seeder; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; +use Spatie\Permission\PermissionRegistrar; class PermissionsSeeder extends Seeder { @@ -16,6 +17,8 @@ class PermissionsSeeder extends Seeder */ public function run() { + // Reset cached roles and permissions. + app()[PermissionRegistrar::class]->forgetCachedPermissions(); $this->createPermissions(); $this->createRoles(); @@ -61,10 +64,10 @@ public function createRoles() 'user.referral', ]; /** @var Role $adminRole */ - $adminRole = Role::updateOrCreate(["name"=>"Admin","color"=>"#fa0000", "power"=>100]); - $supportRole = Role::updateOrCreate(["name"=>"Support-Team","color"=>"#00b0b3","power"=>50]); - $clientRole = Role::updateOrCreate(["name"=>"Client","color"=>"#008009","power"=>10]); - $userRole = Role::updateOrCreate(["name"=>"User","color"=>"#0052a3","power"=>10]); + $adminRole = Role::create(["name"=>"Admin","color"=>"#fa0000", "power"=>100]); + $supportRole = Role::create(["name"=>"Support-Team","color"=>"#00b0b3","power"=>50]); + $clientRole = Role::create(["name"=>"Client","color"=>"#008009","power"=>10]); + $userRole = Role::create(["name"=>"User","color"=>"#0052a3","power"=>10]); $adminRole->givePermissionTo(Permission::findByName('*')); diff --git a/themes/default/views/admin/roles/index.blade.php b/themes/default/views/admin/roles/index.blade.php index 45412bc5e..0fc9d8002 100644 --- a/themes/default/views/admin/roles/index.blade.php +++ b/themes/default/views/admin/roles/index.blade.php @@ -69,10 +69,10 @@ data: 'name' }, { - data: 'usercount' + data: 'users_count' }, { - data: 'permissionscount' + data: 'permissions_count' }, { data: 'power' From ee5b99ce7153f25a007274e50cf73cb7c892a3f1 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sun, 1 Oct 2023 09:59:32 -0400 Subject: [PATCH 239/514] fix: :bug: Fix the infinite credits exploit in development. --- app/Listeners/Verified.php | 4 ++-- app/Models/User.php | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/Listeners/Verified.php b/app/Listeners/Verified.php index 9ed9bf37d..c6e759127 100644 --- a/app/Listeners/Verified.php +++ b/app/Listeners/Verified.php @@ -7,7 +7,6 @@ class Verified { private $server_limit_after_verify_email; - private $credits_reward_after_verify_email; /** @@ -29,9 +28,10 @@ public function __construct(UserSettings $user_settings) */ public function handle($event) { - if (! $event->user->email_verified_reward) { + if (!$event->user->email_verified_reward) { $event->user->increment('server_limit', $this->server_limit_after_verify_email); $event->user->increment('credits', $this->credits_reward_after_verify_email); + $event->user->update(['email_verified_reward' => true]); } } } diff --git a/app/Models/User.php b/app/Models/User.php index 584da0151..246cb0c5a 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -290,8 +290,7 @@ public function verifyEmail() public function reVerifyEmail() { $this->forceFill([ - 'email_verified_at' => null, - 'email_verified_reward' => true + 'email_verified_at' => null ])->save(); } From 754d4f178fe57799feec6f314935cea61cadec17 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sun, 22 Oct 2023 11:11:49 -0400 Subject: [PATCH 240/514] Add encrypted migrations and closes #897 --- ...23_03_26_215801_create_mollie_settings.php | 2 +- ...3_03_04_135248_create_pay_pal_settings.php | 8 ++-- ...23_03_04_181917_create_stripe_settings.php | 4 +- app/Settings/MailSettings.php | 9 +++- ..._01_181334_create_pterodactyl_settings.php | 4 +- ...2023_02_01_181453_create_mail_settings.php | 2 +- .../views/admin/settings/index.blade.php | 44 +++++++++++-------- 7 files changed, 42 insertions(+), 31 deletions(-) diff --git a/app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php b/app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php index b32027e30..a3b6bfd04 100644 --- a/app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php +++ b/app/Extensions/PaymentGateways/Mollie/migrations/2023_03_26_215801_create_mollie_settings.php @@ -6,7 +6,7 @@ class CreateMollieSettings extends SettingsMigration { public function up(): void { - $this->migrator->add('mollie.api_key', null); + $this->migrator->addEncrypted('mollie.api_key', null); $this->migrator->add('mollie.enabled', false); } diff --git a/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php b/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php index 3c0110579..db7d0bdd5 100644 --- a/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php +++ b/app/Extensions/PaymentGateways/PayPal/migrations/2023_03_04_135248_create_pay_pal_settings.php @@ -11,10 +11,10 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); - $this->migrator->add('paypal.client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID') : null); - $this->migrator->add('paypal.client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SECRET') : null); - $this->migrator->add('paypal.sandbox_client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID') : null); - $this->migrator->add('paypal.sandbox_client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET') : null); + $this->migrator->addEncrypted('paypal.client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:CLIENT_ID') : null); + $this->migrator->addEncrypted('paypal.client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SECRET') : null); + $this->migrator->addEncrypted('paypal.sandbox_client_id', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_CLIENT_ID') : null); + $this->migrator->addEncrypted('paypal.sandbox_client_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:PAYPAL:SANDBOX_SECRET') : null); $this->migrator->add('paypal.enabled', false); } diff --git a/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php b/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php index 1483732b4..c320c80db 100644 --- a/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php +++ b/app/Extensions/PaymentGateways/Stripe/migrations/2023_03_04_181917_create_stripe_settings.php @@ -9,9 +9,9 @@ public function up(): void { $table_exists = DB::table('settings_old')->exists(); - $this->migrator->add('stripe.secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:SECRET') : null); + $this->migrator->addEncrypted('stripe.secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:SECRET') : null); $this->migrator->add('stripe.endpoint_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_SECRET') : null); - $this->migrator->add('stripe.test_secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET') : null); + $this->migrator->addEncrypted('stripe.test_secret_key', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:TEST_SECRET') : null); $this->migrator->add('stripe.test_endpoint_secret', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:STRIPE:ENDPOINT_TEST_SECRET') : null); $this->migrator->add('stripe.enabled', false); } diff --git a/app/Settings/MailSettings.php b/app/Settings/MailSettings.php index 1ea8309da..90b5a3283 100644 --- a/app/Settings/MailSettings.php +++ b/app/Settings/MailSettings.php @@ -80,12 +80,17 @@ public static function getOptionInputData() ], 'mail_password' => [ 'label' => 'Mail Password', - 'type' => 'string', + 'type' => 'password', 'description' => 'The password of your mail server.', ], 'mail_encryption' => [ 'label' => 'Mail Encryption', - 'type' => 'string', + 'type' => 'select', + 'options' => [ + 'null' => 'None', + 'tls' => 'TLS', + 'ssl' => 'SSL' + ], 'description' => 'The encryption of your mail server.', ], 'mail_from_address' => [ diff --git a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php index 3de541759..f3b5b37d2 100644 --- a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php +++ b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php @@ -10,8 +10,8 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); - $this->migrator->add('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); + $this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); + $this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); $this->migrator->add('pterodactyl.panel_url', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:URL') : env('PTERODACTYL_URL', '')); $this->migrator->add('pterodactyl.per_page_limit', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT') : 200); } diff --git a/database/settings/2023_02_01_181453_create_mail_settings.php b/database/settings/2023_02_01_181453_create_mail_settings.php index 56953b780..8437a61ae 100644 --- a/database/settings/2023_02_01_181453_create_mail_settings.php +++ b/database/settings/2023_02_01_181453_create_mail_settings.php @@ -13,7 +13,7 @@ public function up(): void $this->migrator->add('mail.mail_host', $table_exists ? $this->getOldValue('SETTINGS::MAIL:HOST') : env('MAIL_HOST', 'localhost')); $this->migrator->add('mail.mail_port', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PORT') : env('MAIL_PORT', 25)); $this->migrator->add('mail.mail_username', $table_exists ? $this->getOldValue('SETTINGS::MAIL:USERNAME') : env('MAIL_USERNAME', '')); - $this->migrator->add('mail.mail_password', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PASSWORD') : env('MAIL_PASSWORD', '')); + $this->migrator->addEncrypted('mail.mail_password', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PASSWORD') : env('MAIL_PASSWORD', '')); $this->migrator->add('mail.mail_encryption', $table_exists ? $this->getOldValue('SETTINGS::MAIL:ENCRYPTION') : env('MAIL_ENCRYPTION', 'tls')); $this->migrator->add('mail.mail_from_address', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_ADDRESS') : env('MAIL_FROM_ADDRESS', 'example@example.com')); $this->migrator->add('mail.mail_from_name', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_NAME') : env('APP_NAME', 'CtrlPanel.gg')); diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 78454fd7c..799e426de 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{ __('Settings') }}</h1> </div> @@ -37,13 +37,13 @@ <div class="card"> <div class="card-header"> <div class="d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fa-tools mr-2"></i>{{ __('Settings') }}</h5> + <h5 class="card-title"><i class="mr-2 fas fa-tools"></i>{{ __('Settings') }}</h5> </div> </div> <div class="card-body"> <!-- Sidebar Menu --> <div class="d-flex w-100"> - <div class="col-2 p-0"> + <div class="p-0 col-2"> <nav class="mt-1"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" data-accordion="false"> @@ -109,17 +109,17 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> </div> <!-- /.sidebar-menu --> <!-- Content in $settings --> - <div class="col-10 p-0"> - <div class="tab-content ml-3" style="width: 100%;"> - <div container class="tab-pane fade container" id="icons" role="tabpanel"> + <div class="p-0 col-10"> + <div class="ml-3 tab-content" style="width: 100%;"> + <div container class="container tab-pane fade" id="icons" role="tabpanel"> <form method="POST" enctype="multipart/form-data" class="mb-3" action="{{ route('admin.settings.updateIcons') }}"> @csrf @method('POST') <div class="row"> - <div class="card ml-5" style="width: 18rem;"> - <span class="h3 text-center">{{ __('FavIcon') }} </span> + <div class="ml-5 card" style="width: 18rem;"> + <span class="text-center h3">{{ __('FavIcon') }} </span> <img src="{{ Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..."> @@ -130,8 +130,8 @@ class="card-img-top" alt="..."> name="favicon" id="favicon"> </div> - <div class="card ml-5" style="width: 18rem;"> - <span class="h3 text-center">{{ __('Icon') }} </span> + <div class="ml-5 card" style="width: 18rem;"> + <span class="text-center h3">{{ __('Icon') }} </span> <img src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..."> @@ -142,8 +142,8 @@ class="card-img-top" alt="..."> class="form-control" name="icon" id="icon"> </div> - <div class="card ml-5" style="width: 18rem;"> - <span class="h3 text-center">{{ __('Login-page Logo') }} </span> + <div class="ml-5 card" style="width: 18rem;"> + <span class="text-center h3">{{ __('Login-page Logo') }} </span> <img src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..."> @@ -155,7 +155,7 @@ class="form-control" name="logo" id="logo"> </div> </div> <div class="row"> - <button class="btn btn-primary ml-3 mt-3">{{ __('Save') }}</button> + <button class="mt-3 ml-3 btn btn-primary">{{ __('Save') }}</button> </div> </form> </div> @@ -182,14 +182,14 @@ class="form-control" name="logo" id="logo"> </div> <div class="col-8"> - <div class="custom-control mb-3 d-flex align-items-center"> + <div class="mb-3 custom-control d-flex align-items-center"> @if ($value['description']) - <i class="fas fa-info-circle mr-4" data-toggle="popover" + <i class="mr-4 fas fa-info-circle" data-toggle="popover" data-trigger="hover" data-placement="top" data-html="true" data-content="{{ $value['description'] }}"></i> @else - <i class="fas fa-info-circle mr-4 invisible"></i> + <i class="invisible mr-4 fas fa-info-circle"></i> @endif <div class="w-100"> @@ -200,6 +200,12 @@ class="form-control" name="logo" id="logo"> value="{{ $value['value'] }}"> @break + @case($value['type'] == 'password') + <input type="password" class="form-control" + name="{{ $key }}" + value="{{ $value['value'] }}"> + @break + @case($value['type'] == 'boolean') <input type="checkbox" name="{{ $key }}" value="{{ $value['value'] }}" @@ -277,7 +283,7 @@ class="custom-select w-100" <div class="col-8"> <div class="w-100"> - <div class="input-group mb-3"> + <div class="mb-3 input-group"> {!! htmlScriptTagJsApi() !!} {!! htmlFormSnippet() !!} @error('g-recaptcha-response') @@ -294,10 +300,10 @@ class="custom-select w-100" <div class="row"> <div class="col-12 d-flex align-items-center justify-content-end"> - <button type="submit" class="btn btn-primary float-right ">Save + <button type="submit" class="float-right btn btn-primary ">Save </button> <button type="reset" - class="btn btn-secondary float-right ml-2">Reset + class="float-right ml-2 btn btn-secondary">Reset </button> </div> </div> From 03f8a7d614853ff350386bf55fadac1970e417f8 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sun, 22 Oct 2023 11:18:50 -0400 Subject: [PATCH 241/514] Closes #911 --- app/Http/Controllers/Admin/UserController.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 4956e565c..b2c7b075a 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -340,6 +340,10 @@ public function toggleSuspended(User $user) { $this->checkPermission(self::SUSPEND_PERMISSION); + if (Auth::user()->id === $user->id) { + return redirect()->back()->with('error', __('You can not suspend yourself!')); + } + try { !$user->isSuspended() ? $user->suspend() : $user->unSuspend(); } catch (Exception $exception) { @@ -361,10 +365,10 @@ public function dataTable(Request $request) return datatables($query) ->addColumn('avatar', function (User $user) { - return '<img width="28px" height="28px" class="rounded-circle ml-1" src="' . $user->getAvatar() . '">'; + return '<img width="28px" height="28px" class="ml-1 rounded-circle" src="' . $user->getAvatar() . '">'; }) ->addColumn('credits', function (User $user) { - return '<i class="fas fa-coins mr-2"></i> ' . $user->credits(); + return '<i class="mr-2 fas fa-coins"></i> ' . $user->credits(); }) ->addColumn('verified', function (User $user) { return $user->getVerifiedStatus(); @@ -378,10 +382,10 @@ public function dataTable(Request $request) $suspendText = $user->isSuspended() ? __('Unsuspend') : __('Suspend'); return ' - <a data-content="' . __('Login as User') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.loginas', $user->id) . '" class="btn btn-sm btn-primary mr-1"><i class="fas fa-sign-in-alt"></i></a> - <a data-content="' . __('Verify') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.verifyEmail', $user->id) . '" class="btn btn-sm btn-secondary mr-1"><i class="fas fa-envelope"></i></a> - <a data-content="' . __('Show') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.show', $user->id) . '" class="btn btn-sm text-white btn-warning mr-1"><i class="fas fa-eye"></i></a> - <a data-content="' . __('Edit') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.edit', $user->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> + <a data-content="' . __('Login as User') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.loginas', $user->id) . '" class="mr-1 btn btn-sm btn-primary"><i class="fas fa-sign-in-alt"></i></a> + <a data-content="' . __('Verify') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.verifyEmail', $user->id) . '" class="mr-1 btn btn-sm btn-secondary"><i class="fas fa-envelope"></i></a> + <a data-content="' . __('Show') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.show', $user->id) . '" class="mr-1 text-white btn btn-sm btn-warning"><i class="fas fa-eye"></i></a> + <a data-content="' . __('Edit') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.users.edit', $user->id) . '" class="mr-1 btn btn-sm btn-info"><i class="fas fa-pen"></i></a> <form class="d-inline" method="post" action="' . route('admin.users.togglesuspend', $user->id) . '"> ' . csrf_field() . ' <button data-content="' . $suspendText . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm ' . $suspendColor . ' text-white mr-1"><i class="far ' . $suspendIcon . '"></i></button> @@ -389,7 +393,7 @@ public function dataTable(Request $request) <form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.users.destroy', $user->id) . '"> ' . csrf_field() . ' ' . method_field('DELETE') . ' - <button data-content="' . __('Delete') . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="' . __('Delete') . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) From a75863909c98772d3a0c18c88da9b7d3902535de Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sun, 22 Oct 2023 11:24:19 -0400 Subject: [PATCH 242/514] Closes #905 --- .../views/admin/products/show.blade.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/themes/default/views/admin/products/show.blade.php b/themes/default/views/admin/products/show.blade.php index 217a27bf9..922d5d018 100644 --- a/themes/default/views/admin/products/show.blade.php +++ b/themes/default/views/admin/products/show.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{__('Products')}}</h1> </div> @@ -28,17 +28,17 @@ <div class="card"> <div class="card-header d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fa-sliders-h mr-2"></i>{{__('Product')}}</h5> + <h5 class="card-title"><i class="mr-2 fas fa-sliders-h"></i>{{__('Product')}}</h5> <div class="ml-auto"> <a data-content="Edit" data-trigger="hover" data-toggle="tooltip" - href="{{ route('admin.products.edit', $product->id) }}" class="btn btn-sm btn-info mr-1"><i + href="{{ route('admin.products.edit', $product->id) }}" class="mr-1 btn btn-sm btn-info"><i class="fas fa-pen"></i></a> <form class="d-inline" onsubmit="return submitResult();" method="post" action="{{ route('admin.products.destroy', $product->id) }}"> {{ csrf_field() }} {{ method_field('DELETE') }} <button data-content="Delete" data-trigger="hover" data-toggle="tooltip" - class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + class="mr-1 btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> </div> </div> @@ -78,7 +78,7 @@ class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> </div> <div class="col-lg-8"> <span style="max-width: 250px;" class="d-inline-block text-truncate"> - <i class="fas fa-coins mr-1"></i>{{ $product->price }} + <i class="mr-1 fas fa-coins"></i>{{ $product->price }} </span> </div> </div> @@ -92,9 +92,9 @@ class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> <div class="col-lg-8"> <span style="max-width: 250px;" class="d-inline-block text-truncate"> @if ($product->minimum_credits == -1) - <i class="fas fa-coins mr-1"></i>{{ $minimum_credits }} + <i class="mr-1 fas fa-coins"></i>{{ $minimum_credits }} @else - <i class="fas fa-coins mr-1"></i>{{ $product->minimum_credits }} + <i class="mr-1 fas fa-coins"></i>{{ $product->minimum_credits }} @endif </span> </div> @@ -213,7 +213,7 @@ class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> <label>{{__('Description')}}</label> </div> <div class="col-lg-8"> - <span class="d-inline-block text-truncate"> + <span class="d-block text-truncate"> {{ $product->description }} </span> </div> @@ -240,7 +240,7 @@ class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> <div class="card"> <div class="card-header"> - <h5 class="card-title"><i class="fas fa-server mr-2"></i>{{__('Servers')}}</h5> + <h5 class="card-title"><i class="mr-2 fas fa-server"></i>{{__('Servers')}}</h5> </div> <div class="card-body table-responsive"> From 654932225a7804e22a3e54176c26392acc892fbf Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sun, 22 Oct 2023 13:48:05 -0400 Subject: [PATCH 243/514] Closes #696 --- .../Controllers/Admin/CouponController.php | 26 ++--- .../Controllers/Admin/PartnerController.php | 5 +- .../Controllers/Admin/ProductController.php | 13 +-- app/Http/Controllers/Admin/RoleController.php | 2 + .../Admin/ShopProductController.php | 6 +- .../Controllers/Admin/TicketsController.php | 20 ++-- app/Http/Controllers/Admin/UserController.php | 47 ++++----- .../Controllers/Admin/VoucherController.php | 26 +++-- .../views/admin/coupons/index.blade.php | 6 +- .../views/admin/products/index.blade.php | 6 +- .../views/admin/servers/index.blade.php | 7 +- .../views/admin/ticket/blacklist.blade.php | 8 +- .../views/admin/ticket/index.blade.php | 6 +- .../views/admin/vouchers/index.blade.php | 8 +- transferusers.php | 95 +++++++++++++++++++ 15 files changed, 197 insertions(+), 84 deletions(-) create mode 100644 transferusers.php diff --git a/app/Http/Controllers/Admin/CouponController.php b/app/Http/Controllers/Admin/CouponController.php index 1287a57c7..71caaf31c 100644 --- a/app/Http/Controllers/Admin/CouponController.php +++ b/app/Http/Controllers/Admin/CouponController.php @@ -185,29 +185,32 @@ public function redeem(Request $request) public function dataTable() { - $query = Coupon::query(); + $query = Coupon::selectRaw(' + coupons.*, + CASE + WHEN coupons.uses >= coupons.max_uses THEN "USES_LIMIT_REACHED" + WHEN coupons.expires_at IS NOT NULL AND coupons.expires_at < NOW() THEN "EXPIRED" + ELSE "VALID" + END as derived_status + '); return datatables($query) ->addColumn('actions', function(Coupon $coupon) { return ' - <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.coupons.edit', $coupon->id).'" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> + <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.coupons.edit', $coupon->id).'" class="mr-1 btn btn-sm btn-info"><i class="fas fa-pen"></i></a> <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('admin.coupons.destroy', $coupon->id).'"> '.csrf_field().' '.method_field('DELETE').' - <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) - ->addColumn('status', function(Coupon $coupon) { - $color = 'success'; - $status = $coupon->getStatus(); + ->addColumn('status', function (Coupon $coupon) { + $color = ($coupon->derived_status == 'VALID') ? 'success' : 'danger'; + $status = str_replace('_', ' ', $coupon->derived_status); - if ($status != __('VALID')) { - $color = 'danger'; - } - - return '<span class="badge badge-'.$color.'">'.str_replace('_', ' ', $status).'</span>'; + return '<span class="badge badge-'.$color.'">'.$status.'</span>'; }) ->editColumn('uses', function (Coupon $coupon) { return "{$coupon->uses} / {$coupon->max_uses}"; @@ -232,6 +235,7 @@ public function dataTable() ->editColumn('code', function (Coupon $coupon) { return "<code>{$coupon->code}</code>"; }) + ->orderColumn('status', 'derived_status $1') ->rawColumns(['actions', 'code', 'status']) ->make(); } diff --git a/app/Http/Controllers/Admin/PartnerController.php b/app/Http/Controllers/Admin/PartnerController.php index 5c4a6babb..8b65eec3f 100644 --- a/app/Http/Controllers/Admin/PartnerController.php +++ b/app/Http/Controllers/Admin/PartnerController.php @@ -122,11 +122,11 @@ public function dataTable() return datatables($query) ->addColumn('actions', function (PartnerDiscount $partner) { return ' - <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.partners.edit', $partner->id).'" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> + <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.partners.edit', $partner->id).'" class="mr-1 btn btn-sm btn-info"><i class="fas fa-pen"></i></a> <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('admin.partners.destroy', $partner->id).'"> '.csrf_field().' '.method_field('DELETE').' - <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) @@ -145,6 +145,7 @@ public function dataTable() ->editColumn('referral_system_commission', function (PartnerDiscount $partner, ReferralSettings $referral_settings) { return $partner->referral_system_commission >= 0 ? $partner->referral_system_commission . '%' : __('Default') . ' ('.$referral_settings->percentage . '%)'; }) + ->orderColumn('user', 'user_id $1') ->rawColumns(['user', 'actions']) ->make(); } diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index ddc91fa8d..6ddae3e38 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -223,17 +223,18 @@ public function destroy(Product $product) public function dataTable() { $query = Product::with(['servers']); + return datatables($query) ->addColumn('actions', function (Product $product) { return ' - <a data-content="'.__('Show').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.products.show', $product->id).'" class="btn btn-sm text-white btn-warning mr-1"><i class="fas fa-eye"></i></a> - <a data-content="'.__('Clone').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.products.clone', $product->id).'" class="btn btn-sm text-white btn-primary mr-1"><i class="fas fa-clone"></i></a> - <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.products.edit', $product->id).'" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> + <a data-content="'.__('Show').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.products.show', $product->id).'" class="mr-1 text-white btn btn-sm btn-warning"><i class="fas fa-eye"></i></a> + <a data-content="'.__('Clone').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.products.clone', $product->id).'" class="mr-1 text-white btn btn-sm btn-primary"><i class="fas fa-clone"></i></a> + <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.products.edit', $product->id).'" class="mr-1 btn btn-sm btn-info"><i class="fas fa-pen"></i></a> <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('admin.products.destroy', $product->id).'"> '.csrf_field().' '.method_field('DELETE').' - <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) @@ -247,7 +248,7 @@ public function dataTable() ->addColumn('eggs', function (Product $product) { return $product->eggs()->count(); }) - ->addColumn('disabled', function (Product $product) { + ->editColumn('disabled', function (Product $product) { $checked = $product->disabled == false ? 'checked' : ''; return ' @@ -264,7 +265,7 @@ public function dataTable() ->editColumn('minimum_credits', function (Product $product, UserSettings $user_settings) { return $product->minimum_credits==-1 ? $user_settings->min_credits_to_make_server : $product->minimum_credits; }) - ->editColumn('oom_killer', function (Product $product, UserSettings $user_settings) { + ->editColumn('oom_killer', function (Product $product) { return $product->oom_killer ? __("enabled") : __("disabled"); }) ->editColumn('created_at', function (Product $product) { diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index b10c1b012..d3553ae20 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -214,6 +214,8 @@ class="fa fas fa-trash"></i></button> ->editColumn('power', function (Role $role){ return $role->power; }) + ->orderColumn('usercount', 'users_count $1') + ->orderColumn('permissionscount', 'permissions_count $1') ->rawColumns(['actions', 'name']) ->make(true); } diff --git a/app/Http/Controllers/Admin/ShopProductController.php b/app/Http/Controllers/Admin/ShopProductController.php index 74c32639a..db07916a8 100644 --- a/app/Http/Controllers/Admin/ShopProductController.php +++ b/app/Http/Controllers/Admin/ShopProductController.php @@ -156,16 +156,16 @@ public function dataTable(Request $request) return datatables($query) ->addColumn('actions', function (ShopProduct $shopProduct) { return ' - <a data-content="' . __('Edit') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.store.edit', $shopProduct->id) . '" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> + <a data-content="' . __('Edit') . '" data-toggle="popover" data-trigger="hover" data-placement="top" href="' . route('admin.store.edit', $shopProduct->id) . '" class="mr-1 btn btn-sm btn-info"><i class="fas fa-pen"></i></a> <form class="d-inline" onsubmit="return submitResult();" method="post" action="' . route('admin.store.destroy', $shopProduct->id) . '"> ' . csrf_field() . ' ' . method_field('DELETE') . ' - <button data-content="' . __('Delete') . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="' . __('Delete') . '" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) - ->addColumn('disabled', function (ShopProduct $shopProduct) { + ->editColumn('disabled', function (ShopProduct $shopProduct) { $checked = $shopProduct->disabled == false ? 'checked' : ''; return ' diff --git a/app/Http/Controllers/Admin/TicketsController.php b/app/Http/Controllers/Admin/TicketsController.php index 3622c22ae..025c58de3 100644 --- a/app/Http/Controllers/Admin/TicketsController.php +++ b/app/Http/Controllers/Admin/TicketsController.php @@ -121,11 +121,12 @@ public function reply(Request $request) public function dataTable() { - $query = Ticket::query(); + $query = Ticket::leftJoin('ticket_categories', 'tickets.ticketcategory_id', '=', 'ticket_categories.id') + ->select(['tickets.*', 'ticket_categories.name as category_name']); return datatables($query) - ->addColumn('category', function (Ticket $tickets) { - return $tickets->ticketcategory->name; + ->addColumn('category', function (Ticket $ticket) { + return $ticket->category_name; }) ->editColumn('title', function (Ticket $tickets) { return '<a class="text-info" href="'.route('admin.ticket.show', ['ticket_id' => $tickets->ticket_id]).'">'.'#'.$tickets->ticket_id.' - '.htmlspecialchars($tickets->title).'</a>'; @@ -139,16 +140,16 @@ public function dataTable() $statusButtonText = ($tickets->status == "Closed") ? __('Reopen') : __('Close'); return ' - <a data-content="'.__('View').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.ticket.show', ['ticket_id' => $tickets->ticket_id]).'" class="btn btn-sm text-white btn-info mr-1"><i class="fas fa-eye"></i></a> + <a data-content="'.__('View').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.ticket.show', ['ticket_id' => $tickets->ticket_id]).'" class="mr-1 text-white btn btn-sm btn-info"><i class="fas fa-eye"></i></a> <form class="d-inline" method="post" action="'.route('admin.ticket.changeStatus', ['ticket_id' => $tickets->ticket_id]).'"> '.csrf_field().' '.method_field('POST').' - <button data-content="'.__($statusButtonText).'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm text-white '.$statusButtonColor.' mr-1"><i class="fas '.$statusButtonIcon.'"></i></button> + <button data-content="'.__($statusButtonText).'" data-toggle="popover" data-trigger="hover" data-placement="top" class="text-white btn btn-sm '.$statusButtonColor.' mr-1"><i class="fas '.$statusButtonIcon.'"></i></button> </form> <form class="d-inline" method="post" action="'.route('admin.ticket.delete', ['ticket_id' => $tickets->ticket_id]).'"> '.csrf_field().' '.method_field('POST').' - <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm text-white btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 text-white btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) @@ -178,7 +179,8 @@ public function dataTable() return ['display' => $tickets->updated_at ? $tickets->updated_at->diffForHumans() : '', 'raw' => $tickets->updated_at ? strtotime($tickets->updated_at) : '']; }) - ->rawColumns(['category', 'title', 'user_id', 'status', 'priority', 'updated_at', 'actions']) + ->orderColumn('category', 'category_name $1') + ->rawColumns(['title', 'user_id', 'status', 'priority', 'updated_at', 'actions']) ->make(true); } @@ -279,12 +281,12 @@ public function dataTableBlacklist() <form class="d-inline" method="post" action="'.route('admin.ticket.blacklist.change', ['id' => $blacklist->id]).'"> '.csrf_field().' '.method_field('POST').' - <button data-content="'.__('Change Status').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm text-white btn-warning mr-1"><i class="fas fa-sync-alt"></i></button> + <button data-content="'.__('Change Status').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 text-white btn btn-sm btn-warning"><i class="fas fa-sync-alt"></i></button> </form> <form class="d-inline" method="post" action="'.route('admin.ticket.blacklist.delete', ['id' => $blacklist->id]).'"> '.csrf_field().' '.method_field('POST').' - <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm text-white btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 text-white btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index b2c7b075a..60a1120fb 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -148,7 +148,7 @@ public function edit(User $user, GeneralSettings $general_settings) */ public function update(Request $request, User $user) { - $request->validate([ + $data = $request->validate([ 'name' => 'required|string|min:4|max:30', 'pterodactyl_id' => "required|numeric|unique:users,pterodactyl_id,{$user->id}", 'email' => 'required|string|email', @@ -179,23 +179,23 @@ public function update(Request $request, User $user) ]); } - if($this->can(self::CHANGE_USERNAME_PERMISSION)){ - $user->name = $request->name; - } - if($this->can(self::CHANGE_CREDITS_PERMISSION)){ - $user->credits = $request->credits; - } - if($this->can(self::CHANGE_PTERO_PERMISSION)){ - $user->pterodactyl_id = $request->pterodactyl_id; - } - if($this->can(self::CHANGE_REFERAL_PERMISSION)){ - $user->referral_code = $request->referral_code; - } - if($this->can(self::CHANGE_EMAIL_PERMISSION)){ - $user->email = $request->email; - } - - $user->save(); + // if($this->can(self::CHANGE_USERNAME_PERMISSION)){ + // $user->name = $request->name; + // } + // if($this->can(self::CHANGE_CREDITS_PERMISSION)){ + // $user->credits = $request->credits; + // } + // if($this->can(self::CHANGE_PTERO_PERMISSION)){ + // $user->pterodactyl_id = $request->pterodactyl_id; + // } + // if($this->can(self::CHANGE_REFERAL_PERMISSION)){ + // $user->referral_code = $request->referral_code; + // } + // if($this->can(self::CHANGE_EMAIL_PERMISSION)){ + // $user->email = $request->email; + // } + + $user->update($data); event(new UserUpdateCreditsEvent($user)); @@ -358,10 +358,12 @@ public function toggleSuspended(User $user) */ public function dataTable(Request $request) { - $query = User::with('discordUser')->withCount('servers'); - // manually count referrals in user_referrals table - $query->selectRaw('users.*, (SELECT COUNT(*) FROM user_referrals WHERE user_referrals.referral_id = users.id) as referrals_count'); - + $query = User::query() + ->withCount('servers') + ->leftJoin('model_has_roles', 'users.id', '=', 'model_has_roles.model_id') + ->leftJoin('roles', 'model_has_roles.role_id', '=', 'roles.id') + ->selectRaw('users.*, roles.name as role_name, (SELECT COUNT(*) FROM user_referrals WHERE user_referrals.referral_id = users.id) as referrals_count') + ->where('model_has_roles.model_type', User::class); return datatables($query) ->addColumn('avatar', function (User $user) { @@ -412,6 +414,7 @@ public function dataTable(Request $request) ->editColumn('name', function (User $user, PterodactylSettings $ptero_settings) { return '<a class="text-info" target="_blank" href="' . $ptero_settings->panel_url . '/admin/users/view/' . $user->pterodactyl_id . '">' . strip_tags($user->name) . '</a>'; }) + ->orderColumn('role', 'role_name $1') ->rawColumns(['avatar', 'name', 'credits', 'role', 'usage', 'actions']) ->make(); } diff --git a/app/Http/Controllers/Admin/VoucherController.php b/app/Http/Controllers/Admin/VoucherController.php index ec6c5232f..b42d5094c 100644 --- a/app/Http/Controllers/Admin/VoucherController.php +++ b/app/Http/Controllers/Admin/VoucherController.php @@ -203,7 +203,7 @@ public function usersDataTable(Voucher $voucher) return '<a class="text-info" target="_blank" href="'.route('admin.users.show', $user->id).'">'.$user->name.'</a>'; }) ->addColumn('credits', function (User $user) { - return '<i class="fas fa-coins mr-2"></i> '.$user->credits(); + return '<i class="mr-2 fas fa-coins"></i> '.$user->credits(); }) ->addColumn('last_seen', function (User $user) { return $user->last_seen ? $user->last_seen->diffForHumans() : ''; @@ -214,28 +214,33 @@ public function usersDataTable(Voucher $voucher) public function dataTable() { - $query = Voucher::query(); + $query = Voucher::selectRaw(' + vouchers.*, + CASE + WHEN (SELECT COUNT(*) FROM user_voucher WHERE user_voucher.voucher_id = vouchers.id) >= vouchers.uses THEN "USES_LIMIT_REACHED" + WHEN vouchers.expires_at IS NOT NULL AND vouchers.expires_at < NOW() THEN "EXPIRED" + ELSE "VALID" + END as derived_status + '); return datatables($query) ->addColumn('actions', function (Voucher $voucher) { return ' - <a data-content="'.__('Users').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.vouchers.users', $voucher->id).'" class="btn btn-sm btn-primary mr-1"><i class="fas fa-users"></i></a> - <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.vouchers.edit', $voucher->id).'" class="btn btn-sm btn-info mr-1"><i class="fas fa-pen"></i></a> + <a data-content="'.__('Users').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.vouchers.users', $voucher->id).'" class="mr-1 btn btn-sm btn-primary"><i class="fas fa-users"></i></a> + <a data-content="'.__('Edit').'" data-toggle="popover" data-trigger="hover" data-placement="top" href="'.route('admin.vouchers.edit', $voucher->id).'" class="mr-1 btn btn-sm btn-info"><i class="fas fa-pen"></i></a> <form class="d-inline" onsubmit="return submitResult();" method="post" action="'.route('admin.vouchers.destroy', $voucher->id).'"> '.csrf_field().' '.method_field('DELETE').' - <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-sm btn-danger mr-1"><i class="fas fa-trash"></i></button> + <button data-content="'.__('Delete').'" data-toggle="popover" data-trigger="hover" data-placement="top" class="mr-1 btn btn-sm btn-danger"><i class="fas fa-trash"></i></button> </form> '; }) ->addColumn('status', function (Voucher $voucher) { - $color = 'success'; - if ($voucher->getStatus() != __('VALID')) { - $color = 'danger'; - } + $color = ($voucher->derived_status == 'VALID') ? 'success' : 'danger'; + $status = str_replace('_', ' ', $voucher->derived_status); - return '<span class="badge badge-'.$color.'">'.$voucher->getStatus().'</span>'; + return '<span class="badge badge-'.$color.'">'.$status.'</span>'; }) ->editColumn('uses', function (Voucher $voucher) { return "{$voucher->used} / {$voucher->uses}"; @@ -253,6 +258,7 @@ public function dataTable() ->editColumn('code', function (Voucher $voucher) { return "<code>{$voucher->code}</code>"; }) + ->orderColumn('status', 'derived_status $1') ->rawColumns(['actions', 'code', 'status']) ->make(); } diff --git a/themes/default/views/admin/coupons/index.blade.php b/themes/default/views/admin/coupons/index.blade.php index d89135cff..cad2aa4e4 100644 --- a/themes/default/views/admin/coupons/index.blade.php +++ b/themes/default/views/admin/coupons/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{__('Coupons')}}</h1> </div> @@ -31,7 +31,7 @@ {{__('Coupons')}} </h5> <a href="{{route('admin.coupons.create')}}" class="btn btn-sm btn-primary"> - <i class="fas fa-plus mr-1"></i> + <i class="mr-1 fas fa-plus"></i> {{__('Create new')}} </a> </div> @@ -82,7 +82,7 @@ function submitResult() { {data: 'status'}, {data: 'code'}, {data: 'value'}, - {data: 'uses'}, + {data: 'uses', sortable: false}, {data: 'expires_at'}, {data: 'created_at'}, {data: 'actions', sortable: false}, diff --git a/themes/default/views/admin/products/index.blade.php b/themes/default/views/admin/products/index.blade.php index a951a46f6..226598041 100644 --- a/themes/default/views/admin/products/index.blade.php +++ b/themes/default/views/admin/products/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{__('Products')}}</h1> </div> @@ -30,9 +30,9 @@ <div class="card-header"> <div class="d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fa-sliders-h mr-2"></i>{{__('Products')}}</h5> + <h5 class="card-title"><i class="mr-2 fas fa-sliders-h"></i>{{__('Products')}}</h5> <a href="{{route('admin.products.create')}}" class="btn btn-sm btn-primary"><i - class="fas fa-plus mr-1"></i>{{__('Create new')}}</a> + class="mr-1 fas fa-plus"></i>{{__('Create new')}}</a> </div> </div> diff --git a/themes/default/views/admin/servers/index.blade.php b/themes/default/views/admin/servers/index.blade.php index 3cfec1641..2a0f8791e 100644 --- a/themes/default/views/admin/servers/index.blade.php +++ b/themes/default/views/admin/servers/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{ __('Servers') }}</h1> </div> @@ -28,10 +28,10 @@ <div class="card-header"> <div class="d-flex justify-content-between"> <div class="card-title "> - <span><i class="fas fa-server mr-2"></i>{{ __('Servers') }}</span> + <span><i class="mr-2 fas fa-server"></i>{{ __('Servers') }}</span> </div> <a href="{{ route('admin.servers.sync') }}" class="btn btn-primary btn-sm"><i - class="fas fa-sync mr-2"></i>{{ __('Sync') }}</a> + class="mr-2 fas fa-sync"></i>{{ __('Sync') }}</a> </div> </div> <div class="card-body table-responsive"> @@ -93,7 +93,6 @@ function submitResult() { }, { data: 'product.name', - sortable: false }, { data: 'suspended' diff --git a/themes/default/views/admin/ticket/blacklist.blade.php b/themes/default/views/admin/ticket/blacklist.blade.php index e26304a16..f501d3995 100644 --- a/themes/default/views/admin/ticket/blacklist.blade.php +++ b/themes/default/views/admin/ticket/blacklist.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{ __('Ticket Blacklist') }}</h1> </div> @@ -29,7 +29,7 @@ <div class="card"> <div class="card-header"> <div class="d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fas fa-users mr-2"></i>{{__('Blacklist List')}}</h5> + <h5 class="card-title"><i class="mr-2 fas fa-users"></i>{{__('Blacklist List')}}</h5> </div> </div> <div class="card-body table-responsive"> @@ -62,7 +62,7 @@ class="fas fa-info-circle"></i></h5> <div class="card-body"> <form action="{{route('admin.ticket.blacklist.add')}}" method="POST" class="ticket-form"> @csrf - <div class="custom-control mb-3 p-0"> + <div class="p-0 mb-3 custom-control"> <label for="user_id">{{ __('User') }}: <i data-toggle="popover" data-trigger="hover" data-content="{{ __('Please note, the blacklist will make the user unable to make a ticket/reply again') }}" class="fas fa-info-circle"></i> @@ -100,7 +100,7 @@ class="fas fa-info-circle"></i></h5> {data: 'user' , name : 'user.name'}, {data: 'status'}, {data: 'reason'}, - {data: 'created_at', sortable: false}, + {data: 'created_at'}, {data: 'actions', sortable: false}, ], fnDrawCallback: function( oSettings ) { diff --git a/themes/default/views/admin/ticket/index.blade.php b/themes/default/views/admin/ticket/index.blade.php index 5dc0bb936..17557a5f5 100644 --- a/themes/default/views/admin/ticket/index.blade.php +++ b/themes/default/views/admin/ticket/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{__('Ticket')}}</h1> </div> @@ -28,9 +28,9 @@ <div class="card-header"> <div class="d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fa-ticket-alt mr-2"></i>{{__('Ticket List')}}</h5> + <h5 class="card-title"><i class="mr-2 fas fa-ticket-alt"></i>{{__('Ticket List')}}</h5> </div> - <a href="{{route("admin.ticket.category.index")}}"><button class="btn btn-primary float-right">+ {{__("Add Category")}}</button></a> + <a href="{{route("admin.ticket.category.index")}}"><button class="float-right btn btn-primary">+ {{__("Add Category")}}</button></a> </div> diff --git a/themes/default/views/admin/vouchers/index.blade.php b/themes/default/views/admin/vouchers/index.blade.php index aa8e679ed..5411d48e7 100644 --- a/themes/default/views/admin/vouchers/index.blade.php +++ b/themes/default/views/admin/vouchers/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{__('Vouchers')}}</h1> </div> @@ -28,9 +28,9 @@ <div class="card-header"> <div class="d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fa-money-check-alt mr-2"></i>{{__('Vouchers')}}</h5> + <h5 class="card-title"><i class="mr-2 fas fa-money-check-alt"></i>{{__('Vouchers')}}</h5> <a href="{{route('admin.vouchers.create')}}" class="btn btn-sm btn-primary"><i - class="fas fa-plus mr-1"></i>{{__('Create new')}}</a> + class="mr-1 fas fa-plus"></i>{{__('Create new')}}</a> </div> </div> @@ -81,7 +81,7 @@ function submitResult() { {data: 'code'}, {data: 'memo'}, {data: 'credits'}, - {data: 'uses'}, + {data: 'uses', sortable: false}, {data: 'expires_at'}, {data: 'actions', sortable: false}, ], diff --git a/transferusers.php b/transferusers.php new file mode 100644 index 000000000..89496b3d3 --- /dev/null +++ b/transferusers.php @@ -0,0 +1,95 @@ +<?php + +/* + * ---------CONFIG---------- + * + * FILL IN THE DATABASE INFORMATION + */ + +function generateRandomString($length = 8) { + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $charactersLength = strlen($characters); + $randomString = ''; + for ($i = 0; $i < $length; $i++) { + $randomString .= $characters[rand(0, $charactersLength - 1)]; + } + return $randomString; +} + + +echo "ENTER YOUR PTERODACTYL DATABASE HOST: "; +$PTERODACTYL_HOST = trim(fgets(STDIN)); +echo "ENTER YOUR PTERODACTYL DATABASE USER: "; +$PTERODACTYL_USER = trim(fgets(STDIN)); +echo "ENTER YOUR PTERODACTYL DATABASE PASSWORD: "; +$PTERODACTYL_PASSWORD = trim(fgets(STDIN)); +echo "ENTER YOUR PTERODACTYL DATABASE DATABASE NAME: "; +$PTERODACTYL_DATABASE = trim(fgets(STDIN)); +$pterodb = new mysqli($PTERODACTYL_HOST, $PTERODACTYL_USER, $PTERODACTYL_PASSWORD, $PTERODACTYL_DATABASE); +if (!$pterodb) { + die('Connect Error (' . mysqli_connect_errno() . ') ' + . mysqli_connect_error()); +} +echo "ENTER YOUR CPGG DATABASE HOST: "; +$CPGG_HOST = trim(fgets(STDIN)); +echo "ENTER YOUR CPGG DATABASE USER: "; +$CPGG_USER = trim(fgets(STDIN)); +echo "ENTER YOUR CPGG DATABASE PASSWORD: "; +$CPPPG_PASSWORD = trim(fgets(STDIN)); +echo "ENTER YOUR CPGG DATABASE DATABASE NAME: "; +$CPGG_DATABASE = trim(fgets(STDIN)); + +$cpggdb = new mysqli($CPGG_HOST, $CPGG_USER, $CPPPG_PASSWORD, $CPGG_DATABASE); +if (!$cpggdb) { + die('Connect Error (' . mysqli_connect_errno() . ') ' + . mysqli_connect_error()); +} + + + +echo "ENTER THE AMOUNT OF CREDITS A USER SHOULD START WITH (default: 250)"; +$init_credits = trim(fgets(STDIN)); +if (empty($init_credits)) { + $init_credits = 250; +} +echo "ENTER THE AMOUNT OF SERVERS A USER SHOULD START WITH (default: 2)"; +$serverlimit = trim(fgets(STDIN)); +if (empty($serverlimit)) { + $serverlimit = 2; +} + + +$userSQL = "SELECT * FROM `users`"; +$pteroUserResult = mysqli_query($pterodb, $userSQL); +$cpggUserResult = mysqli_query($cpggdb, $userSQL); + +while ($pterouser = $pteroUserResult->fetch_assoc()) { + $id = $pterouser["id"]; + $username = $pterouser["username"]; + $email = $pterouser['email']; + $password = $pterouser['password']; + $now = date("Y-m-d H:i:s"); + $role = "member"; + $referral_code = generateRandomString(); + try { + if ($pterouser["root_admin"]) { + $role = "admin"; + } + $checkusersql = mysqli_query($cpggdb, "SELECT * FROM `users` WHERE `email` = '$email'"); + if (mysqli_num_rows($checkusersql) > 0) { + echo "User ".$email." exists. Skipping! \n"; + } else { + + + $sql = "INSERT INTO `users` (`id`, `name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `avatar`, `email`, `email_verified_at`, `password`, `remember_token`, `created_at`, `updated_at`, `ip`, `last_seen`, `discord_verified_at`, `suspended`, `referral_code`) VALUES (NULL, '$username', '$role', '$init_credits', '$serverlimit', '$id', NULL, '$email', NULL, '$password', NULL, '$now', NULL, NULL, NULL, NULL, '0', '$referral_code')"; + $res = mysqli_query($cpggdb, $sql); + echo "User ".$email." created \n"; + } + + } catch (Exception $e) { + echo "Fail: " . $e; + } +} + + +?> \ No newline at end of file From f3a341fa1bac3d4321f870791fbe4b89995a93b3 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sat, 28 Oct 2023 11:59:00 -0400 Subject: [PATCH 244/514] Fix role search --- app/Http/Controllers/Admin/RoleController.php | 9 +++------ themes/default/views/admin/roles/index.blade.php | 10 +++++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index d3553ae20..cc75f1788 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -182,8 +182,7 @@ public function destroy(Role $role) */ public function dataTable() { - $query = Role::query()->withCount(['users', 'permissions']); - + $query = Role::query()->withCount(['users', 'permissions'])->get(); return datatables($query) ->editColumn('id', function (Role $role) { @@ -205,17 +204,15 @@ class="fa fas fa-trash"></i></button> ->editColumn('name', function (Role $role) { return "<span style='background-color: $role->color' class='badge'>$role->name</span>"; }) - ->editColumn('usercount', function ($query) { + ->editColumn('users_count', function ($query) { return $query->users_count; }) - ->editColumn('permissionscount', function ($query){ + ->editColumn('permissions_count', function ($query){ return $query->permissions_count; }) ->editColumn('power', function (Role $role){ return $role->power; }) - ->orderColumn('usercount', 'users_count $1') - ->orderColumn('permissionscount', 'permissions_count $1') ->rawColumns(['actions', 'name']) ->make(true); } diff --git a/themes/default/views/admin/roles/index.blade.php b/themes/default/views/admin/roles/index.blade.php index 213a7724d..b327fce9b 100644 --- a/themes/default/views/admin/roles/index.blade.php +++ b/themes/default/views/admin/roles/index.blade.php @@ -1,16 +1,16 @@ @extends('layouts.main') @section('content') - <div class="main py-4"> + <div class="py-4 main"> @can('admin.roles.write') - <div class="d-flex justify-content-end my-3"> + <div class="my-3 d-flex justify-content-end"> <a href="{{route('admin.roles.create')}}" class="btn btn-primary"><i class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> </div> @endcan - <div class="card card-body border-0 shadow table-wrapper table-responsive"> + <div class="border-0 shadow card card-body table-wrapper table-responsive"> <h2 class="mb-4 h5">{{ __('Roles') }}</h2> <div class="card-body table-responsive"> @@ -48,8 +48,8 @@ class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> columns: [ {data: 'id'}, {data: 'name'}, - {data: 'usercount'}, - {data: 'permissionscount'}, + {data: 'users_count'}, + {data: 'permissions_count'}, {data: 'power'}, {data: 'actions' , sortable : false}, ], From 3b99ae527f8078eff5579f8557aa6ae1b6f60e44 Mon Sep 17 00:00:00 2001 From: Ferks-FK <fernandokaiquecnp2014@gmail.com> Date: Sat, 28 Oct 2023 13:03:23 -0400 Subject: [PATCH 245/514] closes #684 --- .../views/admin/overview/index.blade.php | 290 +++++++++--------- 1 file changed, 150 insertions(+), 140 deletions(-) diff --git a/themes/default/views/admin/overview/index.blade.php b/themes/default/views/admin/overview/index.blade.php index 5a6fc1d53..e9c4debfe 100644 --- a/themes/default/views/admin/overview/index.blade.php +++ b/themes/default/views/admin/overview/index.blade.php @@ -4,7 +4,7 @@ <!-- CONTENT HEADER --> <section class="content-header"> <div class="container-fluid"> - <div class="row mb-2"> + <div class="mb-2 row"> <div class="col-sm-6"> <h1>{{__('Admin Overview')}}</h1> </div> @@ -33,22 +33,22 @@ <section class="content"> <div class="container-fluid"> - <div class="row mb-3"> + <div class="mb-3 row"> <div class="col-md-3"> - <a href="https://discord.gg/4Y6HjD2uyU" class="btn btn-dark btn-block px-3"><i - class="fab fa-discord mr-2"></i> {{__('Support server')}}</a> + <a href="https://discord.gg/4Y6HjD2uyU" class="px-3 btn btn-dark btn-block"><i + class="mr-2 fab fa-discord"></i> {{__('Support server')}}</a> </div> <div class="col-md-3"> - <a href="https://CtrlPanel.gg/docs/intro" class="btn btn-dark btn-block px-3"><i - class="fas fa-link mr-2"></i> {{__('Documentation')}}</a> + <a href="https://CtrlPanel.gg/docs/intro" class="px-3 btn btn-dark btn-block"><i + class="mr-2 fas fa-link"></i> {{__('Documentation')}}</a> </div> <div class="col-md-3"> - <a href="https://github.com/ControlPanel-gg/dashboard" class="btn btn-dark btn-block px-3"><i - class="fab fa-github mr-2"></i> {{__('Github')}}</a> + <a href="https://github.com/ControlPanel-gg/dashboard" class="px-3 btn btn-dark btn-block"><i + class="mr-2 fab fa-github"></i> {{__('Github')}}</a> </div> <div class="col-md-3"> - <a href="https://CtrlPanel.gg/docs/Contributing/donating" class="btn btn-dark btn-block px-3"><i - class="fas fa-money-bill mr-2"></i> {{__('Support CtrlPanel')}}</a> + <a href="https://CtrlPanel.gg/docs/Contributing/donating" class="px-3 btn btn-dark btn-block"><i + class="mr-2 fas fa-money-bill"></i> {{__('Support CtrlPanel')}}</a> </div> </div> @@ -59,7 +59,7 @@ class="fas fa-money-bill mr-2"></i> {{__('Support CtrlPanel')}}</a> <div class="info-box-content"> <span class="info-box-text">{{__('Servers')}} - <i class="fas fa-info-circle mr-4" data-toggle="popover" + <i class="mr-4 fas fa-info-circle" data-toggle="popover" data-trigger="hover" data-placement="top" data-html="true" data-content="{{ __("This shows the total active servers and the total servers. Total active servers are all servers which are not suspended") }}"></i> @@ -77,7 +77,7 @@ class="fas fa-money-bill mr-2"></i> {{__('Support CtrlPanel')}}</a> <div class="info-box-content"> <span class="info-box-text">{{__('Users')}} - <i class="fas fa-info-circle mr-4" data-toggle="popover" + <i class="mr-4 fas fa-info-circle" data-toggle="popover" data-trigger="hover" data-placement="top" data-html="true" data-content="{{ __("This shows the total active Users and the total Users. Total active Users are all Users which are not suspended") }}"></i> @@ -92,7 +92,7 @@ class="fas fa-money-bill mr-2"></i> {{__('Support CtrlPanel')}}</a> <div class="col-12 col-sm-6 col-md-3"> <div class="info-box"> <span class="info-box-icon bg-warning elevation-1"><i - class="fas fa-coins text-white"></i></span> + class="text-white fas fa-coins"></i></span> <div class="info-box-content"> <span class="info-box-text">{{__('Total')}} {{ $credits_display_name }}</span> @@ -123,15 +123,15 @@ class="fas fa-coins text-white"></i></span> <div class="card-header"> <div class="d-flex justify-content-between"> <div class="card-title "> - <span><i class="fas fa-kiwi-bird mr-2"></i>{{__('Pterodactyl')}}</span> + <span><i class="mr-2 fas fa-kiwi-bird"></i>{{__('Pterodactyl')}}</span> </div> <a href="{{route('admin.overview.sync')}}" class="btn btn-primary btn-sm"><i - class="fas fa-sync mr-2"></i>{{__('Sync')}}</a> + class="mr-2 fas fa-sync"></i>{{__('Sync')}}</a> </div> </div> - <div class="card-body py-1"> + <div class="py-1 card-body"> @if ($deletedNodesPresent) - <div class="alert alert-danger m-2"> + <div class="m-2 alert alert-danger"> <h5><i class="icon fas fa-exclamation-circle"></i>{{ __('Warning!') }}</h5> <p class="mb-2"> {{ __('Some nodes got deleted on pterodactyl only. Please click the sync button above.') }} @@ -166,42 +166,44 @@ class="fas fa-sync mr-2"></i>{{__('Sync')}}</a> </table> </div> <div class="card-footer"> - <span><i class="fas fa-sync mr-2"></i>{{__('Last updated :date', ['date' => $syncLastUpdate])}}</span> + <span><i class="mr-2 fas fa-sync"></i>{{__('Last updated :date', ['date' => $syncLastUpdate])}}</span> </div> </div> <div class="card"> <div class="card-header"> <div class="d-flex justify-content-between"> <div class="card-title "> - <span><i class="fas fa-ticket-alt mr-2"></i>{{__('Latest tickets')}}</span> + <span><i class="mr-2 fas fa-ticket-alt"></i>{{__('Latest tickets')}}</span> </div> </div> </div> - <div class="card-body py-1"> + <div class="py-1 card-body"> @if(!$tickets->count())<span style="font-size: 16px; font-weight:700">{{__('There are no tickets')}}.</span> @else - <table class="table"> - <thead> - <tr> - <th>{{__('Title')}}</th> - <th>{{__('User')}}</th> - <th>{{__('Status')}}</th> - <th>{{__('Last updated')}}</th> - </tr> - </thead> - <tbody> + <div class="overflow-auto"> + <table class="table"> + <thead> + <tr class="text-nowrap"> + <th>{{__('Title')}}</th> + <th>{{__('User')}}</th> + <th>{{__('Status')}}</th> + <th>{{__('Last updated')}}</th> + </tr> + </thead> + <tbody> - @foreach($tickets as $ticket_id => $ticket) - <tr> - <td><a class="text-info" href="{{route('admin.ticket.show', ['ticket_id' => $ticket_id])}}">#{{$ticket_id}} - {{$ticket->title}}</td> - <td><a href="{{route('admin.users.show', $ticket->user_id)}}">{{$ticket->user}}</a></td> - <td><span class="badge {{$ticket->statusBadgeColor}}">{{$ticket->status}}</span></td> - <td>{{$ticket->last_updated}}</td> - </tr> - @endforeach + @foreach($tickets as $ticket_id => $ticket) + <tr class="text-nowrap"> + <td><a class="text-info" href="{{route('admin.ticket.show', ['ticket_id' => $ticket_id])}}">#{{$ticket_id}} - {{$ticket->title}}</td> + <td><a href="{{route('admin.users.show', $ticket->user_id)}}">{{$ticket->user}}</a></td> + <td><span class="badge {{$ticket->statusBadgeColor}}">{{$ticket->status}}</span></td> + <td>{{$ticket->last_updated}}</td> + </tr> + @endforeach - </tbody> - </table> + </tbody> + </table> + </div> @endif </div> </div> @@ -209,14 +211,14 @@ class="fas fa-sync mr-2"></i>{{__('Sync')}}</a> <div class="card-header"> <div class="d-flex justify-content-between"> <div class="card-title "> - <span><i class="fas fa-server mr-2"></i>{{__('CtrlPanel.gg')}}</span> + <span><i class="mr-2 fas fa-server"></i>{{__('CtrlPanel.gg')}}</span> </div> </div> - <div class="card-body py-1"> + <div class="py-1 card-body"> </div> <div class="card-footer"> - <span><i class="fas fa-info mr-2"></i>{{__("Version")}} {{config("app.version")}} - {{config("BRANCHNAME")}}</span> + <span><i class="mr-2 fas fa-info"></i>{{__("Version")}} {{config("app.version")}} - {{config("BRANCHNAME")}}</span> </div> </div> </div> @@ -226,13 +228,13 @@ class="fas fa-sync mr-2"></i>{{__('Sync')}}</a> <div class="card-header"> <div class="d-flex justify-content-between"> <div class="card-title "> - <span><i class="fas fa-server mr-2"></i>{{__('Individual nodes')}}</span> + <span><i class="mr-2 fas fa-server"></i>{{__('Individual nodes')}}</span> </div> </div> </div> - <div class="card-body py-1"> + <div class="py-1 card-body"> @if ($perPageLimit) - <div class="alert alert-danger m-2"> + <div class="m-2 alert alert-danger"> <h5><i class="icon fas fa-exclamation-circle"></i>{{ __('Error!') }}</h5> <p class="mb-2"> {{ __('You reached the Pterodactyl perPage limit. Please make sure to set it higher than your server count.') }}<br> @@ -240,39 +242,41 @@ class="fas fa-sync mr-2"></i>{{__('Sync')}}</a> {{ __('Note') }}: {{ __('If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on CtrlPanel. Try clicking the button below.') }} </p> <a href="{{route('admin.servers.sync')}}" class="btn btn-primary btn-md"><i - class="fas fa-sync mr-2"></i>{{__('Sync servers')}}</a> + class="mr-2 fas fa-sync"></i>{{__('Sync servers')}}</a> </div> @endif - <table class="table"> - <thead> - <tr> - <th>{{__('ID')}}</th> - <th>{{__('Node')}}</th> - <th>{{__('Server count')}}</th> - <th>{{__('Resource usage')}}</th> - <th>{{ $credits_display_name . ' ' . __('Usage') ." (".__('per month').")"}}</th> - </tr> - </thead> - <tbody> - @foreach($nodes as $nodeID => $node) + <div class="overflow-auto"> + <table class="table"> + <thead> + <tr class="text-nowrap"> + <th>{{__('ID')}}</th> + <th>{{__('Node')}}</th> + <th>{{__('Server count')}}</th> + <th>{{__('Resource usage')}}</th> + <th>{{ $credits_display_name . ' ' . __('Usage') ." (".__('per month').")"}}</th> + </tr> + </thead> + <tbody> + @foreach($nodes as $nodeID => $node) + <tr> + <td>{{$nodeID}}</td> + <td>{{$node->name}}</td> + <td>{{$node->activeServers}}/{{$node->totalServers}}</td> + <td>{{$node->usagePercent}}%</td> + <td>{{$node->activeEarnings}}/{{$node->totalEarnings}}</td> + </tr> + @endforeach + </tbody> + <tfoot> <tr> - <td>{{$nodeID}}</td> - <td>{{$node->name}}</td> - <td>{{$node->activeServers}}/{{$node->totalServers}}</td> - <td>{{$node->usagePercent}}%</td> - <td>{{$node->activeEarnings}}/{{$node->totalEarnings}}</td> + <td class="text-nowrap" colspan="2"><span style="float: right; font-weight: 700">{{__('Total')}} ({{__('active')}}/{{__('total')}}):</span></td> + <td>{{$counters['servers']->active}}/{{$counters['servers']->total}}</td> + <td>{{$counters['totalUsagePercent']}}%</td> + <td>{{$counters['earnings']->active}}/{{$counters['earnings']->total}}</td> </tr> - @endforeach - </tbody> - <tfoot> - <tr> - <td colspan="2"><span style="float: right; font-weight: 700">{{__('Total')}} ({{__('active')}}/{{__('total')}}):</span></td> - <td>{{$counters['servers']->active}}/{{$counters['servers']->total}}</td> - <td>{{$counters['totalUsagePercent']}}%</td> - <td>{{$counters['earnings']->active}}/{{$counters['earnings']->total}}</td> - </tr> - </tfoot> - </table> + </tfoot> + </table> + </div> <hr style="width: 100%; height:2px; border-width:0; background-color:#6c757d; margin-top: 0px;"> </div> </div> @@ -280,11 +284,11 @@ class="fas fa-sync mr-2"></i>{{__('Sync servers')}}</a> <div class="card-header"> <div class="d-flex justify-content-between"> <div class="card-title "> - <span><i class="fas fa-file-invoice-dollar mr-2"></i>{{__('Latest payments')}}</span> + <span><i class="mr-2 fas fa-file-invoice-dollar"></i>{{__('Latest payments')}}</span> </div> </div> </div> - <div class="card-body py-1"> + <div class="py-1 card-body"> <div class="row"> @if($counters['payments']['lastMonth']->count()) <div class="col-md-6" style="border-right:1px solid #6c757d"> @@ -293,16 +297,46 @@ class="fas fa-sync mr-2"></i>{{__('Sync servers')}}</a> data-content="{{ __('Payments in this time window') }}:<br>{{$counters['payments']['lastMonth']->timeStart}} - {{$counters['payments']['lastMonth']->timeEnd}}" class="fas fa-info-circle"></i> </span> + <div class="overflow-auto"> + <table class="table"> + <thead> + <tr class="text-nowrap"> + <th><b>{{__('Currency')}}</b></th> + <th>{{__('Number of payments')}}</th> + <th>{{__('Total amount')}}</th> + </tr> + </thead> + <tbody> + @foreach($counters['payments']['lastMonth'] as $currency => $income) + <tr> + <td>{{$currency}}</td> + <td>{{$income->count}}</td> + <td>{{$income->total}}</td> + </tr> + @endforeach + </tbody> + </table> + </div> + </div> + @endif + @if($counters['payments']['lastMonth']->count()) <div class="col-md-6"> + @else <div class="col-md-12"> @endif + <span style="margin:auto; display:table; font-size: 18px; font-weight:700">{{__('This month')}}: + <i data-toggle="popover" data-trigger="hover" data-html="true" + data-content="{{ __('Payments in this time window') }}:<br>{{$counters['payments']['thisMonth']->timeStart}} - {{$counters['payments']['thisMonth']->timeEnd}}" + class="fas fa-info-circle"></i> + </span> + <div class="overflow-auto"> <table class="table"> <thead> - <tr> + <tr class="text-nowrap"> <th><b>{{__('Currency')}}</b></th> <th>{{__('Number of payments')}}</th> <th>{{__('Total amount')}}</th> </tr> </thead> <tbody> - @foreach($counters['payments']['lastMonth'] as $currency => $income) + @foreach($counters['payments']['thisMonth'] as $currency => $income) <tr> <td>{{$currency}}</td> <td>{{$income->count}}</td> @@ -311,35 +345,7 @@ class="fas fa-info-circle"></i> @endforeach </tbody> </table> - <hr style="width: 100%; height:1px; border-width:0; background-color:#6c757d; margin-top: -16px"> </div> - @endif - @if($counters['payments']['lastMonth']->count()) <div class="col-md-6"> - @else <div class="col-md-12"> @endif - <span style="margin:auto; display:table; font-size: 18px; font-weight:700">{{__('This month')}}: - <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('Payments in this time window') }}:<br>{{$counters['payments']['thisMonth']->timeStart}} - {{$counters['payments']['thisMonth']->timeEnd}}" - class="fas fa-info-circle"></i> - </span> - <table class="table"> - <thead> - <tr> - <th><b>{{__('Currency')}}</b></th> - <th>{{__('Number of payments')}}</th> - <th>{{__('Total amount')}}</th> - </tr> - </thead> - <tbody> - @foreach($counters['payments']['thisMonth'] as $currency => $income) - <tr> - <td>{{$currency}}</td> - <td>{{$income->count}}</td> - <td>{{$income->total}}</td> - </tr> - @endforeach - </tbody> - </table> - <hr style="width: 100%; height:1px; border-width:0; background-color:#6c757d; margin-top: -16px"> </div> </div> @@ -349,20 +355,52 @@ class="fas fa-info-circle"></i> <div class="card-header"> <div class="d-flex justify-content-between"> <div class="card-title "> - <span><i class="fas fa-hand-holding-usd mr-2"></i>{{__('Tax overview')}}</span> + <span><i class="mr-2 fas fa-hand-holding-usd"></i>{{__('Tax overview')}}</span> </div> </div> </div> - <div class="card-body py-1"> + <div class="py-1 card-body"> @if($counters['taxPayments']['lastYear']->count()) <span style="margin:auto; display:table; font-size: 18px; font-weight:700">{{__('Last year')}}: <i data-toggle="popover" data-trigger="hover" data-html="true" data-content="{{ __('Payments in this time window') }}:<br>{{$counters['taxPayments']['lastYear']->timeStart}} - {{$counters['taxPayments']['lastYear']->timeEnd}}" class="fas fa-info-circle"></i> </span> + <div class="overflow-auto"> + <table class="table"> + <thead> + <tr class="text-nowrap"> + <th><b>{{__('Currency')}}</b></th> + <th>{{__('Number of payments')}}</th> + <th><b>{{__('Base amount')}}</b></th> + <th><b>{{__('Total taxes')}}</b></th> + <th>{{__('Total amount')}}</th> + </tr> + </thead> + <tbody> + @foreach($counters['taxPayments']['lastYear'] as $currency => $income) + <tr> + <td>{{$currency}}</td> + <td>{{$income->count}}</td> + <td>{{$income->price}}</td> + <td>{{$income->taxes}}</td> + <td>{{$income->total}}</td> + </tr> + @endforeach + </tbody> + </table> + </div> + <hr style="width: 100%; height:2px; border-width:0; background-color:#6c757d; margin-top: 0px; margin-bottom: 8px"> + @endif + <span style="margin:auto; display:table; font-size: 18px; font-weight:700">{{__('This year')}}: + <i data-toggle="popover" data-trigger="hover" data-html="true" + data-content="{{ __('Payments in this time window') }}:<br>{{$counters['taxPayments']['thisYear']->timeStart}} - {{$counters['taxPayments']['thisYear']->timeEnd}}" + class="fas fa-info-circle"></i> + </span> + <div class="overflow-auto"> <table class="table"> <thead> - <tr> + <tr class="text-nowrap"> <th><b>{{__('Currency')}}</b></th> <th>{{__('Number of payments')}}</th> <th><b>{{__('Base amount')}}</b></th> @@ -371,7 +409,7 @@ class="fas fa-info-circle"></i> </tr> </thead> <tbody> - @foreach($counters['taxPayments']['lastYear'] as $currency => $income) + @foreach($counters['taxPayments']['thisYear'] as $currency => $income) <tr> <td>{{$currency}}</td> <td>{{$income->count}}</td> @@ -382,35 +420,7 @@ class="fas fa-info-circle"></i> @endforeach </tbody> </table> - <hr style="width: 100%; height:2px; border-width:0; background-color:#6c757d; margin-top: 0px; margin-bottom: 8px"> - @endif - <span style="margin:auto; display:table; font-size: 18px; font-weight:700">{{__('This year')}}: - <i data-toggle="popover" data-trigger="hover" data-html="true" - data-content="{{ __('Payments in this time window') }}:<br>{{$counters['taxPayments']['thisYear']->timeStart}} - {{$counters['taxPayments']['thisYear']->timeEnd}}" - class="fas fa-info-circle"></i> - </span> - <table class="table"> - <thead> - <tr> - <th><b>{{__('Currency')}}</b></th> - <th>{{__('Number of payments')}}</th> - <th><b>{{__('Base amount')}}</b></th> - <th><b>{{__('Total taxes')}}</b></th> - <th>{{__('Total amount')}}</th> - </tr> - </thead> - <tbody> - @foreach($counters['taxPayments']['thisYear'] as $currency => $income) - <tr> - <td>{{$currency}}</td> - <td>{{$income->count}}</td> - <td>{{$income->price}}</td> - <td>{{$income->taxes}}</td> - <td>{{$income->total}}</td> - </tr> - @endforeach - </tbody> - </table> + </div> <hr style="width: 100%; height:2px; border-width:0; background-color:#6c757d; margin-top: 0px;"> </div> </div> From 62149da1f5e71afc8736b6a15c5435d3bb25d2a3 Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:48:15 -0300 Subject: [PATCH 246/514] mercadopago payment add init --- .../MercadoPago/MercadoPagoExtension.php | 225 ++++++++++++++++++ .../MercadoPago/MercadoPagoSettings.php | 34 +++ ..._24_120635_create_mercadopago_settings.php | 18 ++ .../MercadoPago/web_routes.php | 18 ++ config/permissions_web.php | 3 + .../BlueInfinity/views/layouts/main.blade.php | 6 +- themes/default/views/layouts/main.blade.php | 6 +- 7 files changed, 306 insertions(+), 4 deletions(-) create mode 100644 app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php create mode 100644 app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php create mode 100644 app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php create mode 100644 app/Extensions/PaymentGateways/MercadoPago/web_routes.php diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php new file mode 100644 index 000000000..7f12d17b5 --- /dev/null +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -0,0 +1,225 @@ +<?php + +namespace App\Extensions\PaymentGateways\MercadoPago; + +use App\Classes\AbstractExtension; +use App\Enums\PaymentStatus; +use App\Events\PaymentEvent; +use App\Events\UserUpdateCreditsEvent; +use App\Models\Payment; +use App\Models\ShopProduct; +use App\Models\User; +use App\Traits\Coupon as CouponTrait; +use Exception; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Redirect; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Http; +use App\Notifications\ConfirmPaymentNotification; + +/** + * Summary of MercadoPagoExtension + */ +class MercadoPagoExtension extends AbstractExtension +{ + use CouponTrait; + + public static function getConfig(): array + { + return [ + "name" => "MercadoPago", + "RoutesIgnoreCsrf" => [ + "payment/MercadoPagoWebhook" + ], + ]; + } + + public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string + { + $user = Auth::user(); + $user = User::findOrFail($user->id); + $url = 'https://api.mercadopago.com/checkout/preferences'; + $settings = new MercadoPagoSettings(); + try { + $response = Http::withHeaders([ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $settings->access_token, + ])->post($url, [ + 'back_urls' => [ + 'success' => route('payment.MercadoPagoChecker'), + 'failure' => route('payment.Cancel'), + 'pending' => route('payment.MercadoPagoChecker'), + ], + 'notification_url' => route('payment.MercadoPagoWebhook'), + 'payer' => [ + 'email' => $user->email, + ], + 'items' => [ + [ + 'title' => "Order #{$payment->id} - " . $shopProduct->name, + 'quantity' => 1, + 'unit_price' => $totalPriceString, + 'currency_id' => $shopProduct->currency_code, + ], + ], + 'metadata' => [ + 'credit_amount' => $shopProduct->quantity, + 'user_id' => $user->id, + 'crtl_panel_payment_id' => $payment->id, + ], + ]); + + if ($response->successful()) { + // preferenceID + $preferenceId = $response->json()['id']; + + // Redirect link + return ("https://www.mercadopago.com/checkout/v1/redirect?preference-id=" . $preferenceId); + } else { + Log::error('MercadoPago Payment: ' . $response->body()); + throw new Exception('Payment failed'); + } + } catch (Exception $ex) { + Log::error('MercadoPago Payment: ' . $ex->getMessage()); + throw new Exception('Payment failed'); + } + } + + static function Checker(Request $request): void + { + // paymentID (not is preferenceID or paymentID for store) + $paymentId = $request->input('payment_id'); + + $MpPayment = self::MpPayment($paymentId, false); + + switch ($MpPayment) { + case "paid": + Redirect::route('home')->with('success', 'Payment successful')->send(); + break; + case "cancelled": + Redirect::route('home')->with('info', 'Your canceled the payment')->send(); + break; + case "processing": + Redirect::route('home')->with('info', 'Your payment is being processed')->send(); + break; + default: + Redirect::route('home')->with('error', 'Your payment is unknown')->send(); + break; + } + } + + static function Webhook(Request $request): JsonResponse + { + $topic = $request->input('topic'); + $msg = 'unset'; + $status = 400; + if ($topic === 'merchant_order') { + $msg = 'ignored'; + $status = 200; + } else if ($topic === 'payment') { + $msg = 'ignored'; + $status = 200; + } else { + try { + $notificationId = $request->input('data.id') ?? $request->input('id') ?? $request->input('payment_id') ?? 'unknown'; + if ($notificationId == 'unknown') { + $msg = 'unknown payment.'; + $status = 400; + } else if ($notificationId == '123456') { + $msg = 'MercadoPago api test'; + $status = 200; + } else { + $MpPayment = self::MpPayment($notificationId, true); + switch ($MpPayment) { + case "paid": + $msg = $MpPayment; + $status = 200; + break; + + case "cancelled": + $msg = $MpPayment; + $status = 200; + break; + + case "processing": + $msg = $MpPayment; + $status = 200; + break; + default: + $msg = 'unknown'; + $status = 400; + break; + } + } + } catch (Exception $ex) { + Log::error('MercadoPago Webhook(IPN) Payment: ' . $ex->getMessage()); + $msg = 'error'; + $status = 500; + } + } + $response = new JsonResponse($msg, $status); + return $response; + } + /** + * Mercado Pago Payment checker + */ + private function MpPayment(string $paymentID, bool $notification): string + { + $MpResponse = "unknown"; + $payment = "unknown"; + $url = "https://api.mercadopago.com/v1/payments/" . $paymentID; + $settings = new MercadoPagoSettings(); + $response = Http::withHeaders([ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $settings->access_token, + ])->get($url); + + if ($response->successful()) { + $mercado = $response->json(); + $status = $mercado->status; + $payment = Payment::findOrFail($mercado->metadata->crtl_panel_payment_id); + $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); + + if ($status == "approved") { + // avoids double additions, if the user enters after the webhook has already added the credits + if ($payment->status !== PaymentStatus::PAID) { + $user = User::findOrFail($payment->user_id); + $payment->update([ + 'status' => PaymentStatus::PAID, + 'payment_id' => $paymentID, + ]); + $payment->save(); + if ($notification) { + $user->notify(new ConfirmPaymentNotification($payment)); + } + event(new PaymentEvent($user, $payment, $shopProduct)); + event(new UserUpdateCreditsEvent($user)); + } + $MpResponse = "paid"; + } else { + if ($status == "cancelled") { + $user = User::findOrFail($payment->user_id); + $payment->update([ + 'status' => PaymentStatus::CANCELED, + 'payment_id' => $paymentID, + ]); + $payment->save(); + event(new PaymentEvent($user, $payment, $shopProduct)); + $MpResponse = "cancelled"; + } else { + $user = User::findOrFail($payment->user_id); + $payment->update([ + 'status' => PaymentStatus::PROCESSING, + 'payment_id' => $paymentID, + ]); + $payment->save(); + event(new PaymentEvent($user, $payment, $shopProduct)); + $MpResponse = "processing"; + } + } + } + return $MpResponse; + } +} diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php new file mode 100644 index 000000000..f9b2059f7 --- /dev/null +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php @@ -0,0 +1,34 @@ +<?php + +namespace App\Extensions\PaymentGateways\MercadoPago; + +use Spatie\LaravelSettings\Settings; + +class MercadoPagoSettings extends Settings +{ + + public bool $enabled = false; + public ?string $access_token; + + public static function group(): string + { + return 'mercadopago'; + } + + public static function getOptionInputData() + { + return [ + 'category_icon' => 'fas fa-dollar-sign', + 'access_token' => [ + 'type' => 'string', + 'label' => 'Access Token Key', + 'description' => 'The Access Token of your Mercado Pago App', + ], + 'enabled' => [ + 'type' => 'boolean', + 'label' => 'Enabled', + 'description' => 'Enable or disable this payment gateway', + ], + ]; + } +} diff --git a/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php b/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php new file mode 100644 index 000000000..f27411c30 --- /dev/null +++ b/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php @@ -0,0 +1,18 @@ +<?php + +use Spatie\LaravelSettings\Migrations\SettingsMigration; + +class CreateMercadoPagoSettings extends SettingsMigration +{ + public function up(): void + { + $this->migrator->addEncrypted('mpago.access_token', null); + $this->migrator->add('mpago.enabled', false); + } + + public function down(): void + { + $this->migrator->delete('mpago.access_token'); + $this->migrator->delete('mpago.enabled'); + } +} diff --git a/app/Extensions/PaymentGateways/MercadoPago/web_routes.php b/app/Extensions/PaymentGateways/MercadoPago/web_routes.php new file mode 100644 index 000000000..a43179a64 --- /dev/null +++ b/app/Extensions/PaymentGateways/MercadoPago/web_routes.php @@ -0,0 +1,18 @@ +<?php + +use Illuminate\Support\Facades\Route; +use App\Extensions\PaymentGateways\MercadoPago\MercadoPagoExtension; + +Route::middleware(['web', 'auth'])->group(function () { + Route::get( + 'payment/MercadoPagoChecker', + function () { + MercadoPagoExtension::Checker(request()); + } + )->name('payment.MercadoPagoChecker'); +}); + + +Route::post('payment/MercadoPagoWebhook', function () { + MercadoPagoExtension::Webhook(request()); +})->name('payment.MercadoPagoWebhook'); diff --git a/config/permissions_web.php b/config/permissions_web.php index f0918375b..4c74caa18 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -118,6 +118,9 @@ 'settings.paypal.read', 'settings.paypal.write', + 'settings.mercadopago.read', + 'settings.mercadopago.write', + 'settings.stripe.read', 'settings.stripe.write', diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index b6549a7a0..3f1e07534 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -258,7 +258,7 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> @endif <!-- lol how do i make this shorter? --> - @canany(['settings.discord.read','settings.discord.write','settings.general.read','settings.general.write','settings.invoice.read','settings.invoice.write','settings.locale.read','settings.locale.write','settings.mail.read','settings.mail.write','settings.pterodactyl.read','settings.pterodactyl.write','settings.referral.read','settings.referral.write','settings.server.read','settings.server.write','settings.ticket.read','settings.ticket.write','settings.user.read','settings.user.write','settings.website.read','settings.website.write','settings.paypal.read','settings.paypal.write','settings.stripe.read','settings.stripe.write','settings.mollie.read','settings.mollie.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) + @canany(['settings.discord.read','settings.discord.write','settings.general.read','settings.general.write','settings.invoice.read','settings.invoice.write','settings.locale.read','settings.locale.write','settings.mail.read','settings.mail.write','settings.pterodactyl.read','settings.pterodactyl.write','settings.referral.read','settings.referral.write','settings.server.read','settings.server.write','settings.ticket.read','settings.ticket.write','settings.user.read','settings.user.write','settings.website.read','settings.website.write','settings.paypal.read','settings.paypal.write','settings.stripe.read','settings.stripe.write','settings.mollie.read','settings.mollie.write','settings.mercadopago.read','settings.mercadopago.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) <li class="nav-header">{{ __('Administration') }}</li> @endcanany @@ -329,7 +329,9 @@ class="nav-link @if (Request::routeIs('admin.roles.*')) active @endif"> 'settings.stripe.read', 'settings.stripe.write', 'settings.mollie.read', - 'settings.mollie.write',]) + 'settings.mollie.write', + 'settings.mercadopago.read', + 'settings.mercadopago.write',]) <li class="nav-item"> <a href="{{ route('admin.settings.index') }}" class="nav-link @if (Request::routeIs('admin.settings.*')) active @endif"> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 9631ef64d..5b44b85a9 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -258,7 +258,7 @@ class="nav-link @if (Request::routeIs('ticket.*')) active @endif"> @endif <!-- lol how do i make this shorter? --> - @canany(['settings.discord.read','settings.discord.write','settings.general.read','settings.general.write','settings.invoice.read','settings.invoice.write','settings.locale.read','settings.locale.write','settings.mail.read','settings.mail.write','settings.pterodactyl.read','settings.pterodactyl.write','settings.referral.read','settings.referral.write','settings.server.read','settings.server.write','settings.ticket.read','settings.ticket.write','settings.user.read','settings.user.write','settings.website.read','settings.website.write','settings.paypal.read','settings.paypal.write','settings.stripe.read','settings.stripe.write','settings.mollie.read','settings.mollie.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) + @canany(['settings.discord.read','settings.discord.write','settings.general.read','settings.general.write','settings.invoice.read','settings.invoice.write','settings.locale.read','settings.locale.write','settings.mail.read','settings.mail.write','settings.pterodactyl.read','settings.pterodactyl.write','settings.referral.read','settings.referral.write','settings.server.read','settings.server.write','settings.ticket.read','settings.ticket.write','settings.user.read','settings.user.write','settings.website.read','settings.website.write','settings.paypal.read','settings.paypal.write','settings.stripe.read','settings.stripe.write','settings.mollie.read','settings.mollie.write','settings.mercadopago.read','settings.mercadopago.write','admin.overview.read','admin.overview.sync','admin.ticket.read','admin.tickets.write','admin.ticket_blacklist.read','admin.ticket_blacklist.write','admin.roles.read','admin.roles.write','admin.api.read','admin.api.write']) <li class="nav-header">{{ __('Administration') }}</li> @endcanany @@ -329,7 +329,9 @@ class="nav-link @if (Request::routeIs('admin.roles.*')) active @endif"> 'settings.stripe.read', 'settings.stripe.write', 'settings.mollie.read', - 'settings.mollie.write',]) + 'settings.mollie.write', + 'settings.mercadopago.read', + 'settings.mercadopago.write',]) <li class="nav-item"> <a href="{{ route('admin.settings.index') }}" class="nav-link @if (Request::routeIs('admin.settings.*')) active @endif"> From 361fbe0af03092bb09edfa787737ad1d466b9edd Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:57:52 -0400 Subject: [PATCH 247/514] add mercadopago img --- .../PaymentGateways/mercadopago_logo.png | Bin 0 -> 344082 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/Extensions/PaymentGateways/mercadopago_logo.png diff --git a/public/images/Extensions/PaymentGateways/mercadopago_logo.png b/public/images/Extensions/PaymentGateways/mercadopago_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9c896983a0fc7c3c6a5e3e6d7cb257505e50c0e3 GIT binary patch literal 344082 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�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�>Ftyh)3LnA{-GX8(qddr|V+ofB07zUT%ZoyrG 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%0Dwe;on;WP5XLGk0L^6-%tWtuGld#|G+`xM@jR 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 MU;bX^T|J-w2ax+H9{>OV literal 0 HcmV?d00001 From 02b77670e20080e006fe00a0e62abe0d50b21a88 Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Thu, 25 Jan 2024 21:20:54 -0300 Subject: [PATCH 248/514] tested mercadopago routes --- .../MercadoPago/MercadoPagoExtension.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php index 7f12d17b5..42540e693 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -60,7 +60,8 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct [ 'title' => "Order #{$payment->id} - " . $shopProduct->name, 'quantity' => 1, - 'unit_price' => $totalPriceString, + // convert shopProduct to float(this is string) + 'unit_price' => floatval($shopProduct->getTotalPrice()), 'currency_id' => $shopProduct->currency_code, ], ], @@ -72,11 +73,8 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct ]); if ($response->successful()) { - // preferenceID - $preferenceId = $response->json()['id']; - // Redirect link - return ("https://www.mercadopago.com/checkout/v1/redirect?preference-id=" . $preferenceId); + Redirect::to($response->json()['init_point'])->send(); } else { Log::error('MercadoPago Payment: ' . $response->body()); throw new Exception('Payment failed'); @@ -178,8 +176,8 @@ private function MpPayment(string $paymentID, bool $notification): string if ($response->successful()) { $mercado = $response->json(); - $status = $mercado->status; - $payment = Payment::findOrFail($mercado->metadata->crtl_panel_payment_id); + $status = $mercado['status']; + $payment = Payment::findOrFail($mercado['metadata']['crtl_panel_payment_id']); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); if ($status == "approved") { From 7012a68265e47092e727358d1e049f386c08b0f2 Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Thu, 25 Jan 2024 21:42:23 -0300 Subject: [PATCH 249/514] mercado pago end --- README.md | 2 +- .../MercadoPago/MercadoPagoExtension.php | 50 +++++-------------- ..._24_120635_create_mercadopago_settings.php | 8 +-- 3 files changed, 17 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index c121ff78a..35c7ab7e4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ### Features -- PayPal, Stripe and Mollie Integration +- PayPal, Stripe, Mollie and MercadoPago Integration - Hourly, Weekely, Monthly, Quarterly and Annual billing Cycles - Referral System - Partner System diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php index 42540e693..c37d640e6 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -60,8 +60,8 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct [ 'title' => "Order #{$payment->id} - " . $shopProduct->name, 'quantity' => 1, - // convert shopProduct to float(this is string) - 'unit_price' => floatval($shopProduct->getTotalPrice()), + // convert to float + 'unit_price' => floatval($totalPriceString), 'currency_id' => $shopProduct->currency_code, ], ], @@ -111,54 +111,29 @@ static function Checker(Request $request): void static function Webhook(Request $request): JsonResponse { $topic = $request->input('topic'); - $msg = 'unset'; - $status = 400; if ($topic === 'merchant_order') { - $msg = 'ignored'; - $status = 200; + // ignore other types IPN + return response()->json(['success' => true]); } else if ($topic === 'payment') { - $msg = 'ignored'; - $status = 200; + // ignore other types IPN + return response()->json(['success' => true]); } else { try { $notificationId = $request->input('data.id') ?? $request->input('id') ?? $request->input('payment_id') ?? 'unknown'; if ($notificationId == 'unknown') { - $msg = 'unknown payment.'; - $status = 400; + return response()->json(['success' => false]); } else if ($notificationId == '123456') { - $msg = 'MercadoPago api test'; - $status = 200; + // mercado pago api test + return response()->json(['success' => true]); } else { - $MpPayment = self::MpPayment($notificationId, true); - switch ($MpPayment) { - case "paid": - $msg = $MpPayment; - $status = 200; - break; - - case "cancelled": - $msg = $MpPayment; - $status = 200; - break; - - case "processing": - $msg = $MpPayment; - $status = 200; - break; - default: - $msg = 'unknown'; - $status = 400; - break; - } + self::MpPayment($notificationId, true); } } catch (Exception $ex) { Log::error('MercadoPago Webhook(IPN) Payment: ' . $ex->getMessage()); - $msg = 'error'; - $status = 500; + return response()->json(['success' => false]); } } - $response = new JsonResponse($msg, $status); - return $response; + return response()->json(['success' => true]); } /** * Mercado Pago Payment checker @@ -166,7 +141,6 @@ static function Webhook(Request $request): JsonResponse private function MpPayment(string $paymentID, bool $notification): string { $MpResponse = "unknown"; - $payment = "unknown"; $url = "https://api.mercadopago.com/v1/payments/" . $paymentID; $settings = new MercadoPagoSettings(); $response = Http::withHeaders([ diff --git a/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php b/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php index f27411c30..5d4cf411c 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php +++ b/app/Extensions/PaymentGateways/MercadoPago/migrations/2024_01_24_120635_create_mercadopago_settings.php @@ -6,13 +6,13 @@ class CreateMercadoPagoSettings extends SettingsMigration { public function up(): void { - $this->migrator->addEncrypted('mpago.access_token', null); - $this->migrator->add('mpago.enabled', false); + $this->migrator->addEncrypted('mercadopago.access_token', null); + $this->migrator->add('mercadopago.enabled', false); } public function down(): void { - $this->migrator->delete('mpago.access_token'); - $this->migrator->delete('mpago.enabled'); + $this->migrator->delete('mercadopago.access_token'); + $this->migrator->delete('mercadopago.enabled'); } } From 6d61ca5ceff04119452b138b5d6ae5b3bead34e0 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 5 Feb 2024 09:27:24 +0100 Subject: [PATCH 250/514] fix installer --- ..._01_181334_create_pterodactyl_settings.php | 6 ++++-- storage/app/public/logo.png | Bin 95961 -> 96827 bytes 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php index f3b5b37d2..9ab9d72f6 100644 --- a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php +++ b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php @@ -10,8 +10,10 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); - $this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); + //$this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); + //$this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); + $this->migrator->add('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); + $this->migrator->add('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); $this->migrator->add('pterodactyl.panel_url', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:URL') : env('PTERODACTYL_URL', '')); $this->migrator->add('pterodactyl.per_page_limit', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT') : 200); } diff --git a/storage/app/public/logo.png b/storage/app/public/logo.png index 1dda9e7c6c5aa69f3b2d512e6a0521aee51f7b3c..8a68b5fbd7c9eb5949713891ee898d7bda8a8eaf 100644 GIT binary patch literal 96827 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 literal 95961 zcmV)0K+eC3P)<h;3K|Lk000e1NJLTq00EEy004Lh1^@s6+~1^^00004XF*Lt006O$ zeEU(80000WV@Og>004&%004{+008|`004nN004b?008NW002DY000@xb3BE2000U( zX+uL$P-t&-Z*ypGa3D!TLm+T+Z)Rz1WdHz3$DNjUR8-d%htIutdZEoQ0#b(FyTAa_ zdy`&8VVD_UC<6{NG_fI~0ue<-nj%P0#DLLIBvwSR5EN9f2P6n6F&ITuEN@2Ei>|D^ z_ww@l<E(G(v-i3C?7h!g7XXr{FPE1FO97C|6YzsPoaqsfQFQD8fB_z0fGGe>Rz|vC zuzLs)$;-`!o*{AqUjza0dRV*yaMRE;fKCVhpQKsoe1Yhg01=zBIT<Vw7l=3|OOP(M z&x)8Dmn>!&C1$=TK@rP|Ibo3vKKm@PqnO#LJhq6%Ij6Hz*<$V$@wQAMN5qJ)hzm2h zoGcOF60t^#FqJFfH{#e-4l@G)6iI9sa9D{VHW4w29}?su;^hF~NC{tY+*d5%WDCTX za!E_i;d2ub1#}&jF5T4HnnCyEWTkKf0>c0%E1Ah>(_PY1)0w;+02c53Su*0<(nUqK zG_|(0G&D0Z{i;y^b@OjZ+}lNZ8Th$p5Uu}<?XUdO8USF-iE6X+i!H7SfX*!d$ld#5 z(>MTtq^NHl*T1?CO*}7&0ztZsv2j*bmJyf3G7=Z`5B*PvzoD<bXCyxEkMhu6Iq^(k zihwSz8!Ig(O~|Kbq%&C@y5XOP_#X%Ubsh#moOlkO!xKe>iKdLpOAxi2$L0#SX*@cY z_n(^h55xYX#km%V()bZjV~l{*bt*u9?FT3d5g^g~#a;iSZ@&02Abxq_DwB(I|L-^b zXThc7C4-yrInE_0gw7K3GZ**7&k~>k0Z0NWkO#^@9q0f<U<Ry!EpP;Gz#I635D*Dg z0~SaGseli%Kpxlx3PCa03HE?$PzM@8GiU|JK_@r`&Vx(f8n^*&gZp3<On_%#7Q6-v z5CmZ%GDLyoAr(jy(ud3-24oMpLB3EB6bZ#b2@nqwLV3_;s2D1Ps-b$Q8TuYN37v<o zK!ea-XbhT$euv({2uy;huoA2V8^a9P3HE_Q;8kz}yavvN3*a4aCENfXg*)K$@HO~0 zJPJR9=MaDp5gMY37$OYB1@T9ska&cTtVfEF3ZwyPMY@qb<R&tT%ph-37!(CXM;W4Q zQJ$z!6brQmwH{T1szx0~b)b4tH&J7#S=2`~8Lf!cN86yi&=KeabQZc0U4d>wx1%qj zZ=)yBuQ3=54Wo^*!gyjLF-e%Um=erBOdIALW)L%unZshS@>qSW9o8Sq#0s#5*edK% z>{;v(b^`kbN5rY%%y90wC>#%$kE_5P!JWYk;U;klcqzOl-UjcFXXA75rT9jCH~u<) z0>40zCTJ7v2qA<d!X`o`p_Oov@PP1=NF=Het%-p|E^#BVl6Z`GnK(v#OOhe!kz7d8 zBq3=B=@980=`QIdnM~FqJCdWw0`d-WGx-Af5&4Y-MZ!qJOM)%2L83;YLt;qcxg=gv zQ_@LtwPdbjh2#mz>yk54cquI@7b&LHdZ`+zlTss6bJ7%PQ)z$cROu4wBhpu-r)01) zS~6}jY?%U?gEALn#wiFzo#H}aQ8rT=DHkadR18&{>P1bW7E`~Y4p3)hWn`DhhRJ5j z*2tcg9i<^OEt(fCg;q*CP8+7ZTcWhYX$fb^_9d-LhL+6BEtPYW<H!}swaML<dnZqq zcau++-zDEE|4;#?pr;V1kfpF+;iAIKQtDFMrL3hzOOG$TrwA+RDF!L7RXnKJuQ;cq ztmL7Tu2iLTL1{*rrtGMkq+G6iMtNF=qGGSYRVi0FtMZgCOLwBD&@1V^^jTF!RZmr+ zYQ5@!>VlfKTBusSTASKKb%HuWJzl+By+?gkLq)?+BTu76<DMp7lcAZYxmUAKb6!hZ zD_m=<R;SjKww$(?cCL1d_5&TVj)Tq`od%s-x)@!CZnEw^-5Ywao`qhbUX9*$eOTX8 zpR2!5f6xGJU~RxNXfPNtBpEsxW*W8_jv3L6e2wyrI*pziYZylv?=tQ){%B%hl48<m za^F<O)Y~-QwA=J|Gd(kwS&i8(bF#U+`3CbY^B2qXmvNTuUv|fWV&P}8)uPAZgQb-v z-?G(m+DgMJ)~eQOgh6ElFiIGgt<l!b)*Gx(S--Whv=P`GxB1Q1&^Foji0#yJ?d6>1 zjmyXF)a;mc^>(B7bo*HQ1NNg1st!zt28YLv>W*y3CdWx9U8f|cqfXDAO`Q48?auQq zHZJR2&bcD49<D{M18y>Ip>EY~kKEPV6Wm+eXFV)D)_R=tM0@&p?(!V*Qu1PXHG9o^ zTY0bZ?)4%01p8F`JoeS|<@<K~!G7L;yZs)l&|JY=(diHTz5I9kKMc?gSQGGLASN&% zuqN<HkZDj}P+u@5I41Z=@aqugkkXL*p*o?$(4H{Ku;{Snu=#M;@UrmH2;+!#5!WIW zBDs-WQP`-ksHUj7m2NBdtel9ph%SsCUZuS%d)1ZI3ae9ApN^4?VaA+@MaPE69*KR= z^k+6O=i<ELYU5^EF08$*XKY7yIeVI8$0_4X#@of0#ZM*JCG1X^PIO4DNSxuiaI3j5 zl01{@lID~BlMf|-N(oPCOU0$erk>=<@RE7GY07EYX@lwd>4oW|Yi!o+Su@M`;WuSK z8LKk71XR(_RKHM1xJ5XYX`fk>`6eqY>qNG6HZQwBM=xi4&Sb88?zd}EYguc1@>KIS z<&CX#T35dwS|7K*XM_5Nf(;WJJvJWRMA($P>8E^?{IdL4o5MGE7bq2MEEwP7v8AO@ zqL5!WvekBL-8R%V?zVyL=G&{be=K4bT`e{#t|)$A!YaA?jp;X)-+bB;zhj`(vULAW z%ue3U;av{94wp%n<(7@__S@Z2PA@Mif3+uO&y|X06?J<Fdxd*PD}5`wsx+#0R=uxI ztiE02T+>#oSi8M;ejj_^(0<4Lt#wLu#dYrva1Y$6_o(k^&}yhSh&h;f@JVA>W8b%o zZ=0JGnu?n~9O4}sJsfnnx7n(>`H13?(iXTy*fM=I`sj`CT)*pTHEgYKqqP+u1IL8N zo_-(u{qS+0<2@%BCt82d{Gqm;(q7a7b>wu+b|!X?c13m#p7cK1({0<`{-e>4hfb-U zsyQuty7Ua;Ou?B?XLHZaol8GAb3Wnxcu!2v{R<HnZuJKC4qWuPc=?k1r3-ydeP=J* zT|RZi=E}*djH{j3EU$I+TlBa8Wbsq`faO5Pb*t-LH>_`T4=x`(GvqLI{-*2AOSimk zUAw*F_TX^n@STz9k<mNsJ5zU4?!LH}d2iwV#s}yJMGvJORy<OC)bO+J&uycYqo>DQ z$NC=!KfXWC8h`dn#xL(D3Z9UkR7|Q&Hcy#Notk!^zVUSB(}`#4&lYA1f0h2V_PNgU zAAWQEt$#LRcH#y9#i!p(Udq2b^lI6wp1FXzN3T;~FU%Lck$-deE#qz9yYP3D3t8{6 z?<+s(e(3(_^YOu_)K8!O1p}D#{JO;G(*OVf32;bRa{vGf6951U69E94oEQKA^+8EQ zK~#8Nl>KRsWLcWui5+({H#0YL^F8*+$gIq)%C4=K8g{dXL$p8)nixRBe3E&uw*ibk zfb?o!34|6v8VI0>=1|}OLyFU4cURSP71>KwE>)G8m3zkGySur&`J&(Nf9!Zz2D5s| z<8k9&JNK;5dG@p1h2Q%2_pVP*&db@^S?P4I%lY|vuB)r7Tnh^exfT}}i{aH*UoC5E zYh`!$Ie8{rm*wK(q8uF^mC0mMzWwcQm+kHC((CoggGZ0b!QNhZ`t)g`kTMwb%Y=02 zq+c1Wl*`M@((m_(U*_H~8yjn-)9I6TvGkXE<?#5Zy!-Y$<(KchS58h&l4dfYot^7) zU26Lbt&N4)^XWz6d}sNKiwouA@}l^*x3pNM)O~%qP_B4)WtXm#JpD8M>UdPToki+7 zz3KD9HHlJwE%$m3E`@hXOG_!=%e)qOeZ~J=3$=-5skc-XNk1Bm$|-#@SYPsVyR>a7 z?{;^0%XB(TJ;tX6)UiNu*R*|!@{J!2hh@w-3|Cgl?#@oe?2>e6q!GU9A6YG@Lq7(< z&ie)FmS{(ZGL{E}(&N6wSY1%(DL8P#7+SymHUBkbra%1dF;<;Ur<|Qrez~MAow7*& zvvbm4P3bf3zO?_H(pl<or+?(@_XjD*u}I*y$X}<kP`be7YI<3^bab%1T$Z|>G8itG zv(wXZJcE<<UkOY4zDS<)^Q&@jbX?Zg*Giv0h#QB8;MLh_>GHcn8Ql)}1Fkt9-KDM| zO#jXp*XwIwbG-okfdjZkrRDr`3VgsS6H?<6@s6`??^51Ee%prv+?K%aK{-A?F8yA= zbo-satKY3-Ofl&njix-{51g(qff%^Cd$gA{L&i@WUS6Vn(q2r@(^rGz+XWx4%tL<1 z-f^^T7w6Kc?|Z?W!^u%upnXgHK8B93uP!pq;_j4uhX)5VtxG$5<&tq)B<&ULpjgt0 z!}JSW5&y)Ilutg(u-%v7LJyb^$K$fHx|(s>-`^{jv+<aSceG7B5ONnhi`TZ-u@?`! z+?S#8!Z_RJ^^J|Pd26d2?jMxrPoDsfPC2C<VR%6wLt{KIQ2z-0c6`H=92+j*g<dJl zI&7!-aZXz=W_25U4xCKiy(IA!<xmFoiDyf6bg{Eku0g&FU?Oj@?Pq7;LZ_QD<cW*G z@2s*F(p8+cPRFtK6MUEM0jlltox!_2%eXG|uz1~phKA6~Y1!J`D*xO6-G9yVarw7@ z{%^x4?%sbG+@&aZ-y!%qEmOwhY%*nR;F0jp&p-cyl7;;hbb3Mgz2N1OCr`q|0~h)r zy@^M*Z*y}qH0^cw-o5Z@X<nRIUtfoI4wKfl%C`-tjI%hp%9uZU_N?rKN3c<7>ae`` z?%SaW^K{nNHy=Cl5@WB|3FA~CGz4gu4U)lGfrP^-B<}R|JPor#8L7g>O7GE7k?r~O z=gBXyr!XEsUzTAMy&lZM^sDRT%{RXl;j%=7hQpPz^K7U5{AWKaKmOs5%DeBpO~YYU zq`kP9a;IJhueC{pTmWbWky@fRp8qPc1(&#sObn>PKt$wi?yVf#XZq1H%#KGHDle1I z@-5f&4QcFuEgz)e9)eqhQSj=T_R1_;8}g2PefnohBapbRs3T}<n}PWP_c4Te1+j>N zUGf>X#NBj#25sO#^Lv%SeelXF<p|+%1_CG;Y|9iRlIX3c)zt@fJ^Fraef;i`e;{&G z);jfdh*PLs%M7^ek5}sDvh5;^!shG@LWbEb0BfI>366n%l#niAdJ?n3<XViQtG>Tv z?8OJ+VxMH(3YaU}D>3&FBvTm0+35sWIKH(1I&9cZq#ZtA0NYb=bO^pE&<;dw#^~ha z7}%{LgdkY*D&)>-U%wAiCxY(4ATFcipjL$WW&A_zFlGb@vB2GaLmXT(aL3!eEx`cn zhXn<niIYxb8VXVwWuLoDc#*vJPi7#lScc<pO5aR3fQA%MJ>cb72!9cWRsxHqD%8k# zsW9LbKj_aS0ul~g$Cb3g_X1()SeX78g)0gRWuBg%!5B_p-Z1t{`Uf+zzdaaZ6-bx0 zUotGKkUoHuc+sV!J+2OMBgSQ!xZ5a&_U{m8a(O)A-4%U>`7)jnn0A3@g_CXR$xwm! zBF_u7nOcK~3&2bz=-%CXfz_4DhnPUTFsOYK_ZP~FxC!j+xA(xFOPD(bSHhn*hITE5 zcaDkaEF>_Ze)&Zc{ulBf;C=;cFN7t15~svl`HW?l-C(gxv%4UcM)QjQ76IZFun!O7 zIZBC%2(3Nd+qD2w0WBDx#WEZX%iiwO^6dH3^4;%!A0D_C0pK_t?C+QT!#!{ir3TuN z*RF4@mm}_nhr4ABhA$61K0YZc2-nf_O8BZUQ9%+ftzF@1P>2Z=`Lpj75-Mg<8o)!- z%GWJRT=kNk4Dxk(weRH3`?PI&*bj}KfNKib&YipW9;+RgY5zd&w+fFSzKx;tHp06S z$pZat`U-d(hm>THeSiP{{a7GY_UWge25AOpg{Mf;ljBpQ-hfUF%EtO8jkozQGwPS< zkCD1R`r(huPyh0#<=4Odkn~s`YIv040Bd<%Lg*qxh?vXKRs#}I(s2=hQfnXbl)*JD zp=lBsElDlLw3gE@HTdz$DESmtmT3?UKHFMD?jVF3oS0=^H4vX=9zkr9G{NCrEzCiX zd0JKkg!OWl$U-0lnY!;x7v@cyWHMed^krZnA*)r$xDF5?Q;;KLL|U~l@3zVQS)TbM zOa`}>M_Pr@0>3S5S*AyQGH^PcdKDHC3dGxPkxe0W#dxPK+vr7qD^c?Uq;G=vO6Eaa z(kN6N3;rn-^1!%Ufbd!&GH0z2%N8FLGG4;hF_kzZR0X324hO?%bO#5AFynrRuE$+G zJ3prn)HOk6w9cMmZsWKv5GWJMxTc*VKQ&@0X*Gyf7lBI`=4}hpckU9ORt|-w-Qu5( zXtW{YqYGsL{9J$ms6@!n?Nh7{-;+aTa|sh)<lRsKLYz!>K>bZnDYz6M!c!tW$J!Q- zr&#*pthk$c(gIoo<4`!#lyZcy2opyz935*((oS$RbU_;|KZ<~NGI$pz9p0zklqU|@ zE^%x{Ymna~o>yToBXHyzJX-(<y1=4qKH91fP%+7<5*NI+9{OT??7PZ|b*yL|Qm0DH z8cHU>EXUxl?UH8FC+;dM%kT)9-34_Tmo{^W52nu;&F&2zGR-OgDi1IyG;;}el#6r5 zz;QDTQNiEY&{DQMPi3I;Kl>qlm{9nO3)jJ4K_^bui~kriV=EtN%1(``Oxy}O;$63D z_-Y1Eo_tPwF3KBkd@Dlbvrj(b_oR%*qwwJ)tn4UV@S@#a=p4muL^>I}aPaDOtJSc) ztQWyNhm}4ZUfON5OmDlqJ4V7)g?s}Za)LEt9eu`Im}u$M@+zPAinYu;6_oHco>kne zq_e$w>+#jq0ulSscx;q_3InpA6kwcE=+UD`u`V=$G?yG`E3{zq`2P2P@AuFQ87LZS zL$|lKV&1C#uZ~yKnA^8+BV@MHP&IB{IzRd8UzR`nvwvGY`0%52T!yTflyQM5DkxrP z2okXMM^MndFnL~hH$i(=3o%U{K($`+!GX7bDIMY>o}Uu#XtbR2?2q}Qy^3k11x+ek zxE5%W(IQIKGOVk@EKGy`N+>}no^=s~DcLsLOFC0aaOn%_L^IRso@y3}g2OloTS2hO zIIIG*hgctHXeCFxyRl@VNpTM$T9);bp217m60!M3Ux~us3J@=u!&nW3F$K%g*2Zvj zmpD$As{n~mBfl^b#<tD0Diacu3^amOc!5_{n9cgiGtdtb>k1fR?OmXFoSdA~HgOFh zi6D?*nh?L@GFYbqb9s4`@{TpbJ4@y40;`F(jlr#nnyT%AP|tM5kg%(kOk1@+&S<~m z>Nuo{;+bQAUA5*|4ylc{l19Zv;<8>DI}K0U?Qa`XyVMCjRVJm&%%UkEN|ms;*Ow6g z*+h$Ap^Rb33gt!8>PEyCs<5Vh3)G$ESX!pSNbS)+RKeb>nk?znq8EG6An=oh6-fst zXK7Q~Al=a3Fj_9-VqJ1~EMomquDBph_<KmZ2K@Fu1TO}p8!`SD2<ccP;HG#DAR~N6 z;9|p7%dV}cxawuv0rzFfP?>4MD%J=24iO-geyXr@Y=n*DXd6|IVo~!xE>r6vzkO2p z2G_}Br^QJ_XrHw0S@>Jl3fi-Z;}okWcu78SNB6SyZ+^?0a#!eNjJwrRmDU|!??Shh zDqpGXHY_O|NZWXkun4X2UL`g>0-mO2aW%aHmj~t7@4p{6+w0$W9qV~eo_zTP3;QH( z@wa@pkAkh0Am6oIaZv`Z5I950I0x6oCEZc``v>HuJ@mu0wkP9Gn`}#5*yPjdnewb4 z&`l^DbQQ|G^&*M;C-4_79^)f?C@b_sIeUA1;qjeUUVZKH!9D^2WK?@;2@M%U8e-*I z6$%93D>RJret5WFCUkgnbEEth|KK0SY>&o9u%Y_3zx?vcvVCj2yz%<$G-xB*hXZWm z|NPJYto-T!?@!AopL~@12K^y`2IvmZf|3&f!JmZL!cDulJ9w3t>LB{8z-z#B(2euA zmw--EU#E`xP9RI1YAKqHmM0=xOf65O6<xT~_tej`j0m(2^D&(XD>ER@a(Rw+m$W1X zeiE(d?PZ!+1i&gnhTk$3e@mFAm%y7gGy<Nwv7G-nL3o@1Lt*TA=zdE3NGGvHC{dpG zSTVdCQtp`Yd$yCZhorOqb;?uoT%c_dmu*vU$SA7i0RdPhmlmJ(h-AhEJ~P~jVBww6 zU0hOQXC%F7gK%~jtE&$oLb=1te}ONkaI!cR6BP=Xy4K0^@-Q%sc8qYb9u<~z()e39 z)domDMbPLP=`2E2Fo@~t3Ap5(%>sf&23vjGG-jqD(w3`?+zf6ICVdGm&{pxndhLsZ z7JqN2&H?GNFx=n>WjL3hm9dB<Tm+9m;q+T^qCgZzS}4+lnz<V2Vi&D=2+pmoK#T|~ zpswX{8OyAWp>CB6grwsuek_kys1HKERBP;%lf%=pvv*c5#Xs7s;1TzNfAqmC0)#4s zp~xR*Y#P#s+0T9}C=?t^v{gJ=gWiXxQ$r=K@5Mb87?q9%l}Pf(HATI_G18evoLZ!u zWuCQWw$OH?43R$XXomtt9I$O+)|Btqh_7B+HqD(Y?s|`4nzc7fk>>~l?lQLs5#zWL zX73z_p1f&>GY&x;XX|$y#i3Xqlwo^(cX<U5q8@p26%^vIm|F4GIN>RN$OFV5gZa}g zp1rJJ-ZMQHTX+wiE-oyQu3O&!)vq$%ufOpI{ByDFAM8aT(n652D{!PUm2Jx!f&+RW zWcH`0$M6E@lNsmUfVOKXphyKil@|&p+tp|@3L$(^fn>c89z4kVqr<}(2nvu`KE%n3 z!q;pweRRG=?}R*3cW`GkTzRZnHw8&Gku0bYQ2<ZnS^{xHf!u<_cnQ5uZh^d*|F6CF zdbxY=eu&%7iO3?uF_nlsub?s85ad+(<kL^EHvTNvmq=VYB(dw3u!BwGUx=D0<6!{O zj;fg$EK9(OKoWAB05h1|oW>hB%R(9aSBvKIznU(H#BbZXJk*_6%`|~*VxPUML9~3< zYdJEYDwHZBHJ@xQ$`gr1rZ92tHbEb_zw^$1dGSt5g350Q-Za)1Lg&4cUSaUmrADIg zkoh%awQl<(@>rMM4&&ur&-%GmK)MJD#zi;Q8s+%=0>qGrnkK1GvYfb$`0eF<f#WJt z+qaMt=_Oq6)m4D7;F93f?q$?!Oc96_<9pMIoI+0qAH)VR?8Esf8Z>E^82c`kgdmWK zs&PqdljD;zIXo&o=RjZvDl2MXC$T<Y<}mlk2?_wktHDjckq9tqi>rusi$e-D8G~)% zP1@l*gLlWzcS)Nx^oMb_Jzj?34M8+Dj}}wr>wu#!91-LaOUsViZ{evDP~A@AlMK-b z?&_*JJ2@>=n45#Yh)OK}A3;yzm&}3|rToUgCNPTqeSTwwh(lCT$BCMQLtM5(xJIEM zO>mAlpB-nJvA7~`hz|;Y5$$*0W05xLzLI|L!enIli;T7RRfM*T<mB12+_fmQSe)bV zSz%#X8NKmZx7Oo4o9!}B+QW0hUEA+FU8-XkS?GZ)?I&%NDcefkz?igwEqNU)^Ex<D zaJY*{_FuRg27TMP9L2YdiRpx|cP&)mYdH#f$Io%1P|B}-#_~;TXnax}X^SjutNG0p z81o!_G7W$2w{4Q0XyKEkeDT?*(f04bL;K?G$)r5nd6v8Flh^9K(IU}hD<ij!6L4m< zf>jRExR69;%K5-k>4EZXN5fzFr+mZT&4XkaL*le1v<OuSRhSeG&B~F!+i&Z(G~w-n zpU&!d?eX<O)p9P;(gJvaV5hkZ3XheQp|B&a8<W~WaOH=Zos#$d{Rg?<xqT~yxqo=T z0QbuGzxUha-rf7r{+>R4UVi-JAD5r}<R`I8?7$EWGXNJCW(*rHmbwgWkh~lO2hk2i z81bCD$VV`XNaKh!z{N-DJL^clKq>A)hFQG74Sfi)PyYsJkOIgB0#%3*_?>p}n9DMd z_QFV@`#$jEF7e9L6KA_gFS1K`5eDYrx5(|iF?)$fw}9pQ+q-GaC&8Fb0iyu2+$n=< zJtE_>t_9MF1gW3A60vRvnU)MfrmzkIYI&&9`K&udrg6oX>f;WRgMjTzgoSOQf0fZn z!&-%vK)g4rBk#Fx;4D!pKBPIG`&f3CbrEK6&xogv1(?1ZQ{TRBrppVM1>>-^G$g$m zb!EJkb4q`E1O2h|js6Ou!-K=P9AZ6CtbXv!muamK(X3@BOdWIE7At@_n{j!GhKquf zk)5?YjLq`w2hS1i;JNLQSy)aR>+~U)`8(7%o#m<YGgV`v$KrM$+|%V`yjD!Lc*Mi2 za=5pLU__%6ZnQ^h=0vxbllzv1W*TPW%ndL(I=hBZI5yxE?G_(wkN5oMIZTSX%r^=M z`Ng+(<u~uWbTO^Nv=ts&AAS1R1(!C7+t}DB8+YzR5Qv|aw|Vz&x&7!-Ir{9g;FpYT z`_`>$EsHaz<GH!6{C$Iy#7h%RXaLw)UAA8<ONCMaq6MSCiN!H%uWl>xUm+t+L~$XV zW0K2qDM$RQ7|WeHjZ?v}?32mq44=6ucR?kFzeSp}pevBZ33JmdFg^+g3aNF?3QZlp zpTSt=gq*&}_&N>^PZCcMNBC)#wT?x38GWWrDy^S<{BillH@;DBZQr3BEa&jqDOSN6 z@EDV(@;Px@WnUPLS2w5<McqmEgZ;9(y%hoE9CTc$z{qw;m(sOmM40hg3rB(C0;Yjp z0D{89$#{910>k$T4KG)GT2@?Fytj_daCzl%golI;bgaO_0|YJ!W{k;{ilY&R)zKJP z5Q1WVr`!T@T)ykdt1iHJ`|Y>OcxAQx;eY<0b03#aKKZ2l=tn;)Km6ej%f}yoB#cAE z23JlwQK-;p%-(^i?a0rxrxC7$*apulNMnHn2d%|bg5rWujkAmdZ`Mg4Dj^%;`|6j~ zjWEH|>cQ{?zVFQMJ(p$iBoRmk5_Lmli6Zs3J88Ygzf4^ct%RN5JbOj661L=TLPSBK z(AM9qKnMZN%J7{7Yh5C4<|U}VwF!)j7HHWk488(HH<c5&S{hD_c0qs*%v6_CE-7%x zD+6$urUK4(>prvJ3Txq;i@b3~kzRnQ=_zE4mq8hyb~LzJ4r3|_@fd<38O((7h8dgK zNlaZlGEos*ff0fgEZ_yT@Ed%9ushv4S2LMlVbCwNqAa(jE|;e|7PdEo37)!yH5Lk5 zeVbqvkE+I4T^`~<ggqhSM~zv+ndXZZj4^3cBqZ7_mY}ER3!ZT|WCE6QEK9<*eGPB5 z7+j%bEsHRG7jrnbW14FOgv{gQ@FbJs9mYvUb57hjcqoI?^18n0WQ85wEvJ!5RjG=` zOByZ5JkR391;;YDBaV{A`dwuew<&QWV54FYMh|W%KyJfSB0xy1FmdAh-kWchtyf-& zo60hz-SO5|l$NP3RMU~yGQ@3vTc#W$jGuVx^GdsDuj3-#UZ7b6$QkUbfQjqYv9X=} zwjZ{8YkRvq`sOzyJWh|W%xrs@F9L`%6coa%vRMM}y&5jMy38>;Rx#wao=62kmY<80 z;FxW_0B7Y(>I5#ZsGbMPqYf?Sj5uXV^BDsRvOJ#W8FqMrZQ?oO5?rvX%2VVd8C%9u z3sZdk?9)%n-TU{-t=qQ)qy4=D$|!MPiQifd_Sc0^u0nL)MJ39GOj-n4#Yx$E4s>rt zVJ6rP`G%GY4JvEUzWgEcE%a4d@Und8QeJuZFoH_B8fTu28|}0|KHH8?cX8>l#F9ba zY902nKr`r%(~Y&dx;|@6oiugg{R9*1wbx!NZ@u+f@twW<&O32e{rx}q0~$9i|N3A5 ztMVs*@+alpci)|LLd~*j4D=H^IHxYzISh(;L#^L-=qH3I9eILzUgWKW(}>vLWVji$ zR=y@^2Fo9WmaaW}Wq<*K$hS18ZnqN##8ggBD%UD;%BU5p{3fk=)E*?zFay&NZ`!=) zE}#_>L3YYhi;_US^dqbNXR@9AzLVgFAkHQPsu0mC5MlKND;Q+-K?z|6k|=l-7L0cF zD~qI**C$Q}Fy`6>A+{uNvMrHr3*>nXfiF?&$JTVdi{>LjWhpQHj~|}AGC=zz;pw_+ zL}QR?(djT0)282Ev%cGB`)~f-X}LtXGQu<-)D=9Ce_RXZ$+~ySHLr^l#53!fp21AO z59d7SaXC9VDu+j>5e#HR^pJ;l-R@F303Y4=AQN{y6uP=M9ZY}g2DDvaIOMZGh9+3t z697HK*u@{T=%q@4DonnwmbZ#U6_m!=#)eC^zZ4J#B*FEnTMA7x^Mk?(A=z-fgO%Rr zx;zJeW|qbk4C!=oNZYzFEb5#hED<iJ(<;cRC|48^>JOfAsa%LVP1E-h4|Gjs**5nG z2cJpfJLf3064XC?z#{K~mzut~xd|hC@H@X#raL?3;DZms=q_DE^XyqUM+3FY%%v&# zz?rzcU}7@+%-7B8vR#&`PhD$7yv{^A@Vv23NGpy9kNNGKOWd#g9wV&2@zz@vzxl0u z%Q+HPM}^)=?|8b8L>?hNF))FR+O!IUg52>rb?LD0Wd-B}qe;I6PQU{bO)LW3w{`rr zCXAQ9XAGILr2;Si56|K+I7FSkZ;G8`6&j*`-Lln6mS2biT9wMn%S>i}`Q*#;?CG;| z7Yk2eFdB}sAf#Ft2jGGWEc6&jXI<iU%hB;6mPlP(vbnX6GICIc(3rf_$!__G^@+Qd zDGdk*_u4(%Macq3v&e(@_Z~dR9JTY)QLJdYdvu&GkoVY6%c|W1@5bZx$Drk`Lo|v6 zVS=-SS{Dii*Vr4BwrYNDW?mul`@jG9%huLr`TVobV}gD6yWc4veDHqxpa0FDmOuT| zKMgZis#&Ia3}Lw3W$+HRCc6=pS+&8f+M23@%O}qvA~l5w2S=1(o9jLQ-~7G_`OVeR zaoG-o|68B$Tw-V5<>3<TQ8QRdJ!&H6s{T#$h#ZwUm^Tx`<kNBxX<B*wpFxtyCFBqY z&k}{iAmK^4&KF5&5{iVQcBP5#pl4MS<;s+G3pk+B;7F&?iiSx!VHUIB(E_P!L|U~+ z_jx#m)+v*4Ma=!zUyu7i>qI~G8p2AAa>zJefT&Zo6oS^|GRV}U9Q)|Rnv|s0XJ4DJ zwk=h_(tj`VdarN=PCn1#h@uEI%6=6fzHw6X1hx8tu^qx>oePt}U&%0a4Ok#t-92D~ z9+t&1!b1y&nq2%rB`78%i0mZD?)ceO=W)b0@z}a`k4V)1Zj87wa;tEt-MDAG?aH`< zmshb8tgqsZtDnqn8ZX-_<7oX1fudQ;kld@`JfUo-!|S$0*^H*{Lgem|2|eg<=buYE zPtUH(`2{pczZYR{!dzUkO%c-6=_S*ZsYVDmUcgjEMaHd#5h1}CxU$Mi+*syr8ZCqf z6<{J>m}khl#8Zc9AAbM)Wf{CUK{K8_eOjij1QKT8O~XN#_FA4o!t$J7%bW`D#mBf$ z8H?Z?=`UeUDnDMfJ%UcWwJoGGkJh&?vupT_f=uN`Me77XCM>+#MDHTc-rYc}J5wRT zK!w&_0V;oS%$FDw`H46pJzqz0W!$+!$K3tToUuF|Fv^M`;CH+<ymt(m;wnCv57Cz~ z_nmlP9_cmKfbdF2z{a!6m}#q}ApX^H^|uQ94t;dr$L%|J!iTy`%}Ty5w}=yu6(H^p zaj}UOLX=I~H0afpY1X4h$o(a@Rs0ML$m<k@^K}X=l`X9oX-t=x<>;D{uN&N0Y~A+N z33kh{eb%8s>a47+J-)cW<PM`0?k}XM&+hB6QZsrPZv!MurJ@!7;UE6}FsTO*?#EjB ziy!{D{11QfKOjUt%zJ^-2-&Lv-JtL?EL>|f$hfOC4|9f@IJp{$O^2-8Q11?6ZBM-% zT;lm>{<c!az|Xv^@h{5v&WqWzS=CTvG-yV^L)SuM9HukynzU7T@KnoSfPC+Bkd5DQ zS#gEI1HTXnaCHNqMD05IF~sr+fvdIAp?}Up#6<-`NL(^=8V~}EwrQEvDwB{{)#4;H z%a!<b9VjHUY_u}8JY*If2HN%fGKRRasF%?%mz1wY>G&v=Zhh-p5gw-?h!%yKo4!un zRxTN~kN#FDq`#CG*Bb95<cJq4qPp-g-n6C>M%C?C;dkkr1X`D;16%-O6}?`cJecIJ zfj>W;f`{ZqV3<o6)^J2S$-8?<TNr!i8G3_WCevM-eL6+pl2$*i^@_(Lx#JQ+#&v^# z+$EBR1CDoGWsG?gl#G2NZkt~jftGj^iYbS*GJo?#7+IEO8%Oz;SNlvKAX*7toVPCf zcYTR=H$8y?+Ain>?fB%>{FV7BjNDMCFIsn{aMVf_wq7z`8Li_gj;RnRFyek9Zp`}? zFMh9*Zk=-cEuLtRy2?tc!{1}xdo|&)0Zrfg?ca{&akjf#VFp7Njy-Tl#Uk!6Esj@S zDXS_&FeL?n3Xymxo@f6UX`Ogqg8>$V5E^Lkw&AWq!|N9OW{onmVianN;;3z<FA5M{ zWm^a$7mu8Ps|(~=q-^=isWc<pX_NHsn8Xs{(h3z{q#?)A)qGMk@;SK6pk^#h?|3OJ z95cNmx|vL<OEsI7N$({b8edWAtr|OcK-o>nXbMWhKj*AcF~N6POrk=XMI4p`9Uuh0 z_~P?e1aH3iM)<96E?2NFDtKsz?^aeuz`J^M(tlvug%%X-E^}8oRVeK-c5$x~FHO|N zPg8{Q3PNK&drWi-SGXV;Nh96ZJ}21eVBD1ochdy#({AXhzchTDCA>t~!2n|hnE1uH zlyI6p3KEIVLcBC{-hA`TH1u_}T7|{``=9>P@-P0yzZB2EV)97f-ZiUc@FVChkf8Xx zq+X<91*F16fxrcD)j}Q67ATUdB1r8>9nd;5m9Q((z8Gx7{D8Ns1sUebcqJ3HnyJ}? z5M&{Zogq{Wvz1*Dtv=T@B8irOgWw<<C&QP4MkAESsb8X!$!`EtfE%-2;s;a00HVo} zUbj|Oz3{AHi4dAySwhKsg^=?C64(+*<nK)_Mh0F+&?Q}#W(ouPX1lyDAwGqLZMy*3 z)GXK35E<jimtV$3ATc@KZuryxF7nx@rUAG1gqhh-o=K51ZLe|SgqPNpa1uu5cTA@- z#jnchifJ^f(b!<9VXi#)`YI6OkIP*l42h@faW?vG>)5;OQ30ssAoIvr+aCK!A516S z`rBvg_wHr?qD9kIal`So?WQqKVd1eU9koE7odc{GRxd;PL|O^e{4T6$^FrdT?$<sH z!E(dQ78p}*wkZn@C`fBorr=Q|6Y6tefyb}d4u!a|R@=^g3+u6c_L2I-thp8;cyYyj z8?iKb-vb`2<X4D{sox3l+b9;RjE924MHITL#^8<?t3t<#RhQj5wwnkA>0<AlcVZ0- z+bIma^?A6xT~<gxq}=iC+hOqT3)y<>tuX%uEe-HWe0M_pa&NDUNV|>@SwPF}(&h*b z>!(fyVQ`se<G0bE#c2hI?l0-&)j#-yGDfgH{ph1|L>Wimw3EJW5X~_vq|Nx!mf$Px z5Xj=OyW3sL8{r_Ym;Zn_3hGd}?KC~3>w=EJIsAyRW1OZ_QW0m_!oc^zBYr#fRhg)~ zH2A^Yj7`~a{G+_(RaHocj>Vlt7_=zurj1%1A0oKNYvc0j!*9eYRS0UC>;gygT9(4O z?GJIR<kJcd1&oT+D!d>^9f3d6K<!gC5NIyry$z1L^6?_=bOOF@*$9r(E?xTKy^653 ztTN;ACMqu-G|<Na!C=8wU}uf9aS8`b7YD!w`%V{@LdEMJ|Koq0&4ZtP_F4HS|Ky*P z|K)%AQF;BfZ(?4pC7t;~OajwpKvawraT5D%z+e&rE<_43na0itBzISuKSGFmW$LZG zSs<<8=f;2J=bi8U-K>QSAeZs;R}COOeG@qD1XcR>mPFesxY};(w3RaIq*_Ii#Lb?C zpFpOO6&Z;Sp>yeLc9L>~OCh3`b>F3tq`P?jyetAs30|#DZPJx55e5vV1g|BrN_-fE z<#{Ll9W=IAzx&;|s`?^~+8$*NAgBR%wa^WSTg_0W5UWVUpbXo531Vgbh4-fjjx7`d zdK7ns^C0#wmIP^JWV$65Et~qCoR{gy#AFx>Duv_<ZP0y^$y6@u)oSumi<Qu1hFW`S zV3SjE3xRrToAl19=;9in(K!Y(g6cjvo*>8&?AaB}Z^%ExEz@w!9M9?=fIz6nv2r5P z;5*0Pu~N`P8>3z6pY3G4J<2C%Bz1KgV6oAc2%-oz%IcGbvX^E`j`<BTjo<|B5@*1& z^i5?VP_|wCIY;9%@dyXXNtWwQIo!W0JBO!*TEYYjmTjF{G1*LK|7<t;tW{y5u+AI@ z<%=I$7U+wyHiooim9%S=v(9fP$DIdLpsbK~KzZxn*KMrdRqpQjSOZqNt92Ktw2XM3 zeEf07N^4j}LZPfTAbp~aJJ7z2U00`;$jG@n?$Uu}bX8Jlf1kFmfYS?Tzfnv{?|fY7 zf_R0Bmbm@7^6WgAWC#ACwV@Rv9x0@@f9tox3_pGQ?VQgvp<K`7@;;?Km*(e+u;y|c z<s0IQXL>E6aA--yQjs4Kf;Y3VtoQ?F!Dkfy6vewvlpS4_!6C~xvCrhqBl#mlGS;;m z)0nsMRnv=4=NFzgSl$0gCn8uTmvtCCYt452?c47l1l&0M5L~&+`EK%I^Ge6#@#uyx zKYaMG(iZvU-SSkeh}D&GCfKb*K5Bau0$TbNU&Uc*$CD&3FbodkNm6mD!Cm~-`mioH zf@?fPsU+fY1E80ZPzG(MbW7Pt1%Y?d=~MH!lQS0dkN(j=O5^_7Kl^79BLBrd{G$-w z=bwKbvnoX3z%qc|k>hpP5E8KvE+sMtn)*96&a~ELXpF(D=J)x<d&|#YCipD*67t@( z()DcM3Es_XU%mTV;_smaIAG>+aGhuqAwYo)ijN_B3kiHGu}jd|7r}4eNjT9Id6o&p zav+@=Xb6fy@X|$a8v*hNg1BNZvM+-&bSG><Ofr2LvlCjboRVSd%F^W^vrvzb@E-l( z2j!ZvxB%XkDPxEw0ukRr7;Jsx8)aF7Wh{qWU|v}Qx$Ka_BWvv`yHA`Gqd_hR*@@C< zwB%K@^jUVEHqf8A89*kNZmub`XqN&-O)>5Z`Y3~8Fe7+kwOEdDCyt6jbd8D2NYBu| zc>tLm8*q`aP~eFZ61<j(Ou~_MpU7aj3Ye_2qHHyP-(@VkEZ26}SKdji5>>?~iAJRa ze4|X6O43q7+*!8E@lsfDw;tQMGV}mS;$aN#Ahztq>r3M7N5uh?>tA<n=X~NJcFu#i z%0-K-o=bFm<UHajOz^avCXZ!Si^$MmCDFG2(6G9CiTC1_uohpu9D{A}K_x;VvW`Gl zhYmLxr@Jr~wMrEc=Qv`akgg8{Q0sSZkgIaUu`X@XO3-zoU=Uw=;F5yu92~u(O*aCK z^ub-;sVD`<Nh^No;!-JDMsTTgxEax9!ERD?9z+dSrnI5}LkZHwb&aq&|Mb(cNIj0M z<>~5@Do4=7`WtVQF*Nh>U;lM^f?%6a-UOITZIgG>sa%Ky(d5Z54AsWv0m$zx*r)=8 zaSv~x4`v`eY^UNW<LG!-{$TC!Fxu^V%QYkgWtz`A?7w$^i_=xmR~%~XFt1jG{Kb35 zm3S4P;G{5QEUjCMT0Uy+AAI;>SzBKzckbRVrzlZ+7o6YIia(Frwq$jc$C!v~;z-=m z)GH5GX>qzi*HtZFvaIe{=OW#`F1@*@#QZ8#E|~J%F87moI*0R>3V4OTGQj*oqSNmW zVu8^1bXI0+XOv)0d}|)HXHV6&q4AHA=a2vRk0V6>`9J^X5h{Q5M}LH=eO5lC!!|ZR zb3g$`?#g_g1-}F#Kn0?H!M3tR0*e&oT^h;V(8>|GhA;@O_MV4&C%Dwu_1kT*4Iw~6 zOP|4>-F?+o+4#Or+|w3->3fkQ2i$R0@$>>|s^7B;BA;adO-q#R$P}W%TN#7tT!-eo z1fsV0+V{VoNyTd}SDS1B&vh;U9SuzLUSXhHLAO_5grIyi{Z|kk68yCji)sTPiSKm- ztU+iJ+<-b{0$s*1Op$VW#2rAWS{Uc#aSTk8qfKa=eXxyM8oCQ4{H(O0ekbF#cGPxt zeYnv~i%G_$0MnHcmxXoOZ?3qnxWcf3l>*nX6>cuQbZm|D<k9Jc%VjU?MH(kYU4<mU zXP_7X50mQ;dN2*&i^#<DPwlR*o}p;UbfmNdvNtC0Dp4xr?Q<8VZ;`so98;}g@y~iQ z8B1Rj0=CU`uFT42Tl+^I@k94Roiu0cDc8daty61bz_Wzh2cJ}sY`wzqa_X|l%W`rG zOo+8DmMPqWN8%RciHl)U_JiNzl`V~h$2hvdPx`wD1NLYe1^SS2(#lY9+$R3cy?a?< zmkCnxyRbs{kk)M`)kz;~mU4SO^G*h&TP(s%_^V9NM=g-dFQi3b9<j1BS4Uo5u+vXI zDVJY-5dktpa1Fp8=Sr+wJQ~RS6i_N2-~477X@%1t1%>0XKwS!)Ap*_c*0J&JZ<m!< zUkyI*V<o@;mwy?hRpD{$IN2uhs6;q!Z4VEFM6;^JB$g~QR$AhY=|y<IOk31}=TU|V zfR+l(Lv)BB(Gm$CBdvnQJdQ~ChxvWx3e8Z4W9&QUTU3PIKcef*5IpDdo+Z6p-cuH# z@-um{xMH2)EB#kNa{GgA|Hf;tr44c0g6oGz`|+HJ*IEwyyL%PqxPr@ETGUS3%lM@c z)2hUnhN8mbomy}|i0}0{iFclqElkeO5&n#Y%A#8`JRmm)1!<tGrN?200^rJ31axNc zIvR`Sip=W`22P9P3lO6={4y>U1;$T*`qP**zw<jkpv&Fz&fD*noxS}4B18ZTFR!&k z1bYRf@hxOhlAjKwp$QOy>}AM6=kmAAI?)NHaS+uUy!+lEwom41f}@2xNH5~Odx@+v zINzC9gb>gUo+XM*sRJexMB(z@h`&9XFH&AarhMbFQiUsP;wf8VQcKtUa~~@qt086r zKx@Ny3I)`LxU@9$#|ZFseUp@_1tLROK@eP`iK%t=xO7>q!BEy-f4vOv-YqLIjvh=# zB6P{46XBlRdki8SVzHcoI1*Qv_X>^`^6G{_8wgW$;w%CW;$Btzka5u#nT>7<EfMiU z!RDl=uDQ74e9z@5s}?NrUYJ}^u45!I3LD4HITwTDF06tuz*@wOWnfB}wf9&8T+W*~ zVX9Bswr7PB1RF)9t5pC2f>-95PCW8NrU-FJr<5;2d-e4_iz`&o#?7&J+!cHozaS^A zR)KI_c$6_@(<8VbEkuL0ee_P!cVU2TE;Z<_&2`{7Kx;=havjs|$*D^w(H`laLS5Xj z9P0zIf{VdF`X;>vcW9IC)9(!6!r--Nwfs~l#0gIoaL2f`sdek`J}_6w(4DAdu?hp) z_|~_|>Z3<x1)Lg#Q(7=R(k!bC5T}*U2X6+vQ@NS$@5h46?tR)LjB>mS&sqk~nYp0D z_IbG=BQ7Y?4YZ2Dt3LRnRjPF|;P-&`4alp|xdty)Hbw}X2zdKVTbKF0^#^}YHe3k? zKJS0_S^4?D{Fmi(l%G@5PrNAe1l+l#u5+HXM5@JDwd^P=1nGJ_WL&X84v*`;AYn~5 z;9mJbgam^rf3lA%6bp;)Gf{ZdF_Hs`MtM)1_b80yk2TFbJd8BXeYxybfsjqt;GPWK zcjj~K4Jn5*ocjnKEDZ8KtN3`Q_V0cty*jsVZ-?fbJ`fKr!e<qHam7u31_gxaq|2Q> zlu_tHupD4*Xb{*&1&;zF)(miSHI{jNe*)d;#+R3#p3aWbCyj#Je6~xgrZXIl9@}Xf zETKwFo?v?K{=N9gY?#)C!XrdZI)C3`@b$TU%AkJhn{O}(3*gK_dG8nRMN1Qr>YyM9 zvce?fk&1nGkVKZGVbI!O2c*PVm)b+iW8-yY)b!Ot@oJ%!pMJa)A3-*&*Sv-dAiq;5 z11!Tb&N@xwvwhHAaZ?w4iSV;be)}93%d8#=oy;MApGA;p;9R<~)a*3()yQ=PsdcR} zVE53lWY+qeo%Gi7P^-_<OVav0VjOfu-6pLoS=O`+5C*!kobS*&*wAJ5hksbcPGUlE zQv}7)JMWYUV|UEJAMtx1;yZ@8os?C>Ry&k<9Sg_1t?5_LiAY@-h^`KqlmeqqKIdMX znB9OWIM?9;kEVU_z3)ZHDCDwWfV=&6pUDQAuna463*>Rb+#=78RKqLb)r3nJD|HHE zVJrOAeuF2xpMo=5C8n3EFU}xt%F*QZyB8jF?pSzCh^tjR8pPE!)y*W!AQKfyB)S@0 z#_hZ2Qj0>xod$%+v?(R<j5)s@zPP=#T7aYL<o%Wgk>&X8s+?SsqrY5s4vx#OKYmi4 zA56*w%R?pT94ku~MFbs!ui`cZa@lth#T6ry4h_%_-F~Kvl0!cMTHK7+TniAgmV~(S z2>iH%0?~o#m#0t5_IJKhwiuUK6|_-pf8>G}@MnmHBCd>35M9FUiL9nma14Lzx5D(( zF6z9{N~V0rEA+s#-(Jy@(3-VPUgroCg~Af=mMK5XRhWV2OO~(Da12Rz0ZvX~iXGEp zLG`G2g?fEfcnq;RCeNOg-Cz8oJbVBB^5KtvTt5BvugfXrP06F{FBS;*3&tdFEZQLM zh-U`zrP6OH8|y<r*(t||<`mLI(!c7)6qkr`{CP!=<n?_N8LlQg8a_6m5g75l#YZzY zp0s8VUo1~XZxFYf`xBN1^I5J|x9t;0!h_O|dWm-~sr>?YZEkL%+>eva(|KJ*rJ}3l zV3656cis5w`r1Z${`5JD#X3y?AZdNqgou0jv_KRdS|ffgVRK_MLhta1aYy-gkCrZe zk4mwgSS|Fiv()Q9Mpt5>Vfc$O=F76Wwi*jWU^JJN%(MZ(xbJ@VyYbHpSP!prfIj>5 zvz&7ktx2E~Gyx?tIBLoZu&h|b;%=M+72=pZw}G<+-y7=S_*BbiakGcYyz@94#My5{ z7y-dL3@x82%qL)s3S;57>FjUXLC{K4<4cM3NByP=2Q_t{C1?pHHO=Z#xcJ*LEiccc z(?oY-S?%7HC|VNERjfiZV+c&GU16c-;R=^k=P?*u&3}E&626+9LMZF!NjHYrMrf9O z1c$vl$J9K2`|UD;NEH?n#%y0DfVM1Bw*G9>=}yp}?lA>UaO%Ry${HArb17j=l&^cO zM|(%qtKWK^Hm$>moLfm5^eLLBW$@c}tI_tkM&OchaaGV}1xHrQ&=#)<3i3y2aM_<M z7bZUGxMU0>yh22`RwHQpaKV_!L`EZ57C~4%M;K6M&Y6<YLu{J-8ACH!&XjV9&+0F5 zREFJfB=a=ZaYe~alJo4i_`M5{WOvTP6<~|xcRs}aS3Ij0i<oeYBDJ@Nm4~LNyR1K` z{fly;@Srs}0Chl$zf76C_+|-VSoRm#2M0-iBUrgd;0q_NEY}5AaVvt0SxCY7i7!LO zX$(%8$M!ENz!?YIpx}|wcNiZ9L6>(+jFa!X+{I_x<pK{a50{vO#DR;;W*swO*adG+ zwNOZ(zA5lXt1EM0nFtMqN1+`1aSadA;;_G&R{=lI-IR#1J4L}+REeS8j)UiH9Uy4- zz}sgis!zG^Q@;Y_fO7W1;Unr(&6HAgUkNAaO$9}~6_&zLX1})Pk>v~7M<jO+{Ox1% z_^a^>$H#HtaTaWEc#A(s)0FP!weX$4t<Sqc!#=o|DtHVIHLEB2xg1Y<#!b0IWWSjk zmTezRp;<TO%WIq(P$Bo@d(GvfrJ}{rc$Vp<U-|TxUwn=cutu7ToDnx1t(0fLS~$66 z{FpdbHJ+ZG)V()Y%lk(j=D1QuE6Xt!vLX_rB1fN)Cf3NTj?SRJ{Mb`Q?UxgVc2?^` zBDOI~DxYn*6DUd^(|ixD=K0P}4y)5ldiLyjdHd~m09u_x(BEwD>$>TRi~=Fr+ze<< z+YbO~kk0~{M+m9LwJ<-hExp0#N{|jm4GwM##zzB~mG5uMnlD?;RPB<NVN$iOTBigf z^Ri!x1BcqbW?5)$_$-12sVG<NBQ6mxiNv}bT<@7c<XOqDP;xn~%NunUDj>|eg0{X4 z@oC175ejid!MGJ%R}gEK@Y+s(JIA5Bp|39)&CQ+USCn}Mv7JJ+6UNPBnZ0}T`!(a) zgFw%z)4nOZWQ+<jC-GMqdzsG4qel^RYOT68Y}@Fy*UB0e&gwV6S%$PpO?HX+Ys%F< zAv00qlsU<;bd#*<v*mdgfwluP5P5Znx-?MS$)+#*tY8rSGA0>OgbVq^!5s9)?>0f| zJI7LZ3InpFFX!Og))*r2a5*CD_%U|QrwA8_s7|1IuLSM9M(|F?M+gJrcPe7E(JkTv zTgpkii<#`zD)pU;h3;|*Oq_T5t+-92!3o+vJuBxYPTnsdOm#tZ5$57N_=?~Ov!J5j zuDC;~_Q~J2N#<m*P5w?GF77LENK0`IyzzA9Y!>950>!TfW(-j(rZQ$=f5rIg#ymxX zpTdkUWg?7&u1o1hym3!O2ZcabUdr&H`?C)}tZfp1XY|?wFSQz6(yR5=+NZ$wjIz)e z@b$<XC(@U|T?Naf?EqI`BxM01<pGjf$L>2i#lmr}#Yy@rV7UA4yXC3YGx-i_`vKP> z&j+;afI9cUpF{4tucp+m^%49KmbQmWx>txeiSDuZ8=I?fyX32cFleHJ18{}3U%Z>C z4-N--_F_D|r))y{$K@|^Gy<C6j(u=vmL>v~%l$vL!3@_b4#v;D&p7k@CR~HJ{sxDd zfC+pppVvFjcY@nn(6M*jw(@EDseQIj`;4vLqbML0r4pk7pnFq(uz#>0m#_UYkL4>E z@>vI-Rj9;!Eo;lp(I>nUe#!v%CRtx+d3gl^qC~9BQ>o(Wt5&cRz0C^oy^|;&|NGm& z{oC2GSc&bry!-BZWtWaQFm_yqe02p;A}wXOcEa+!EIS<&Ry=#zXnot!CDj(STJm0r z?f}jhh5)xtLI#rGExrYxGeEOJX?aZNfH?>T5y*jCT0)yl8fu5ZDCtd~0ZZFJGTNK- zamW%uLHf8kc(2boaNrV|%&%Y3xCkWDt2NqpCzD%#edKDAYGqdpimOl<oziFER}cmR zn1KFcH9CdNC4{C%xL`W+I6txk5_PdUBxpu7?R8&=+S~zV|2~YuFOZ$mezgfTO;^>p z2SjGCZ(K{pcId*|c<Zf9VD=SEq*d@Kpq3F9%epnF%L&yG4f(xfKF+hKY3kp$ZTiGl z-gqOv^Fsv33Ap0AeT9ef7pK6*Ie-i5jg>+B990>+?aXI+XYliVv`gBVadZsGXArks znyKk8bJjd}VtWbgOjyWpbxXK(*7LGFnnKguF%UP!(|jhF_i^(;;KIcN_S~B?^i!hw z2%3}c3K89CXxrdpuREX|)e3|s#W|<qF%upiufRH;NC(aV)r0A@R8Ed3C?pUl`Nc)C zQQQMFQm;6ng(hCn0Xtgz;YHuY=q$|OclKQ{e$JV7ftgFf@2YUXoSg%k!mRgx{NpGK z9vLFT*QGfXml%KXDV7K@)*WVDp)Kwgq;bB(&89N-9^<+QZ*W`}#CsW*N&sW3k`!T2 z9l~3N(*u`WOkrDHw9$p9Oy7Gi^C{Pqqj2&ll*3Oy&1bGJN$;31p>P}%|M_42RoMlH zcffar#Tj)T)6dU|+vWF(_{?=qobw+lBH~Xj$`L<8?~HTk6xv;JHPYTexn#@~-quGF zU@EVubZy99H1S%JCP6DJ<KTPJd9}DK7IECThvyLIT{oIMz<ny>=^{#qdrl0N8{W|J z&FaV@j-&;Uv8sG$*-^}RFMi02rD<JG@@M0NyVNI7_E>gTXu1j9Hn_6Nx`eYG9j&aE zz32N94?Ia;S^1@HvV7;;{Jl!L2uRwd;$)rjIxP|3>rU$|kDLPmz!m`WzI_`Fl!h5( zcvh@=BvCv2?r+^!S|u*qbrp!aCf`GVL`$c#9BV0n(D>B)Y=9LuqHoOHyO+$vizxc! zWMYjcOD@ySUjo$7ep|M8%lFw}+gtEm>YSxZ9@4sCz$U3s=I9C9K(d%?O`6Nl(r)rP zw8}>{PUC_&lrO<}7dC1<?zpA!8<&V}60t2St{53Tb=eQ+18jt4C62N@TKyPJu`5wY zKtKFp*@vLc5Z(%jxEmP64#?wV<u#_hlkN*@50rKCi(h2-u)^b%_FQmze1k-`#MtVN zu)b9oP26d;&53+n9!|K1K@qQZy8Qj`moA9dq0T^-`Xk6FdrI5RWkSZ&7e6{AW6-BQ zz=|^LxL*IGkK$sw6!|IJ6*Fp?-W4R#9;r($&3jt`CGsjfn#)R|g(R=cipzESMxC8q zU<I$1)wN;S+4o~-le(I$r)7!tz<ALqz#L>kNYr_ZF7Fi*u2P{{FdlFZO)Vc*L;3Oh z=~IM~pHy|s{GNyY;6?D;V?$i}y-XQ?4bIhamuFae=UPh@&m+i)mx=hLLnmX;DOY?G zC&U?7Y-z!YQ!)wL<!^~u#uqoJ@451h-&(O+cnW^!rd&N0YlQK0Y3v3TqATp4{^&=U z`_f{QhP6P2rDK7nSPGVe!o~eR;${vb)S`4;7*_?3YyGqTgKOyA3H0u!!gFv`W++^| zvc#9WIP5+UHyJK-DM)mADL~F({u4I<g4_Gx#Uc1}21B|a{TY3^q&>T|<td8K4(-~f z&Cg+4&%moM`Td0S{yw7br(9F=oO6lup-+)PxMeUa?id_{RfLDD;hsFh3MY*)kspXt zDhp&xocR<e3UT*FgtzcEG|c-tvX!wLRJ@uHkk+kR-XLvj1q*lcJI8j8;$Xbxm^S<c zc-czZ<U8A3Ycx$&;(ceo8t;<FXhpa|Fw3)no8xD>D*FcSwppQ~_aPPoX?Av=GxS*K z;G6RzOTDT%o7TQtpTfwyZZ!L)5Q>L}@?GFy+buIVIvQeuP?$}&<9@4HLUS3Sf<#8D z@KAHK@tQ%NccrCb9w$vd{`lh@&t5@FaI%}f#*nLp^FoT-053yLQ-6~r!E$MR%HY9Y zwS#k^rmq6SGJR{D&+WZs=h-;YwGzx@ar!;wDVl0(l1l2<1}%<Ob}K*HX37DPcy7df zW5SZgxeF25c?SoKhJ-L>BKEi;nq@oaO^7&;Ai}vfL*MTySH7V@e9j<}xsu+44lx^J z33L%4R}f`fNHA;}{pl}%S&lyZFpOi$z@O5Vtd%G4HNUlhv}l~4a0QMN_RdW>8S0rw zGN!DUF)eu&R$50FXo^mxp2Gl6ApR2s)Cu(*AgoTl_#)cqDeXK#$jRJwsd?;$8tN&? zzDGNCTb)Z(v_;p08^E+iVr@`Y;6-TTB?EOpgui2Lye<GDAjpb4VHPC6fHAMFt;2-t zY7%kK_Z}D*+!5wvQgLvc+%(mJc=Acpi@Ma)W3y$z6PGRa`el6$p)_781Io~iR`XT{ zr8Uwce^!1CmLvF%4$$fWZ|42x+H7BBR{36tN*2u#e=H}KspUbS`GyGTD}E|e8om+i zyJhlA7nXf+^^|)!vR_Ey!I*A3U&gpT``f=Q`!H&i2Hg(ggU5plV+Hm#`38?3l>=zk zd9n%go$u*W-!Wq-gAd-3H}e$Y0PmMyeKnKa=PD75Uz7pbq5yHMWH8Q?Iae1~CUq`> z!wL{-N;tZ2<Aiwe;1c}06z2TirM^Az-qlrF8~c>M0}g3%?D2d+-XqGGf<rnS#TMZ! zlhi5_PWDaJu_+lccU^jJj+`9qmxGD8K}z9@;2=9HE3hR`+w%ci;8x>&ZwPPU?zlK6 zji1SA{heg|jxh0^Kgn}OAEQ8PSupmgn_xMuJoDwwWj!^`tjyW_#xtzbXRjPd0!*AR zSD`nZE+}11@@$V_*Fuqp>x%N(_Gtx+>ywis$~XegssNN;Y`Y4vE_U(8Hmr`<@-8l0 z(pk1J7xtaeXytJ`qTMrvJTy_W#>-4zS{`izU5V<aYu7dycmV{lzWdI*<>2Tb8kgmS z#Zq`B{7MWhSf25Q3J9M~EAtl>1BZkR3_r}>@)%M!cjH5(lw+`-`MO$2{Os9a9)kiz z0#;MCc!$h&;3B$aY+V4LMo`;Q%b-t5Yib^NFQLnL)Di`(XVRo^lw(Mn%+J;O<8w=+ z)@GbLY(vQ8mmzz+2ofd(6<@P`HLY<lb2kshmkomSAZ&fh6Mftw42UhW-h<GO(MI9| z;<AmJp-!4hXs-Nn*{^P@1?txV((<vLix8e}3r}Lz<#CF|>CWUc5tp`Wnp?KZCuLM> zq6&*>r{r_4#l!FXY^i3o0>o7_C!{;3KbE2YI=hEyOPDTg&OC@PqQ4TTurp-bbteY$ z{1Gqg<1U3z9oF7Z#D;Nz(|)tttU+|jtjYp}M!k-n8kTb+)05-kKAW|*F@mF0c3_yE z1bt_76_EB(mX^`BWf*FZGIj7cdqAA$>X-ff!z_=asFa;CMnI{A+49uw*wJs@Xjd2D zjeVtYT^{4gRG`@(uHY;04Y54PYn;LhnMIuGA#~!ilN%<SW!*6D+i$&9bPFjM)KX>C zerv=PJZHk5F_Xz&$s`c+d*F+6Q8I98`vBpwi*n$%$`$Sk8kc*!c~HERQ9If0oZKnp z=IfEPEpvU8>%5<HcjDGmrILIK5GThKK#sXq!vzd#YT5Mt5Jq<bJ_I+oobT8LUv{W> zm;72BzB{Il32<@WhpsDeF7q~&qapwmhnD0q(v1)lpJa$}@9geH3Cl_=2a(IuNoFjF zu|hDYnNU9e7|NMdVm(F>nov&~e{;7E<1<D)`_48ftQ)V1#UtGFE7j#{>2G)%&!%bh zrEYHGa0~Jc^GVNI8d?Zh#yjvxc$6686&5mjd4a;k0}zd08Lp78u1?g=?swUB|JqKk zhO1g36Y&RO(_QSBH7xv#Ym5oTM%ekWHsR1&8I2!TCSGC=X<fK>-R>wfY@lm@(=i%u z+{1^j<jYweGi(L#y#4buPBYDPA)pxm+C9vY%Xi*Q@4bckuD-VuwTvnp!dPbIwBYYH zIHo0Wo=t0={>pt8?*O)E)8^f^%Q6)p3?fC;cJYwnT$3#$;@La`*Zh>D&`AxX_nmD~ z>O0ZoN9sjVEe7AIMW_9<0ZRMKBjWJew#R(~p(sF{Gl28dc?Jg&;Vlz34U~{&kZ1hX zoh4(}8gbQ1?_1w0s10!uoQXIf)j9QeE{=@eHd%jGSkYFsSgobIfAmMCOBtu+a|xzQ z&ixwtr?nuQUpXWG5KH55X4$AUUUMC4aY)p(+cU&s*3$L~!p%uh-62{q&wll*EVDe~ zz55{4KouY|wD_-Sr*IJ)Y@=iI6?h51z`O?0wJ9b+;=Zd-fFm0IXzUK%I+k!1n3m%J zPU&v)*lK>e6WBR653Td-ZGD(}jvuGJ;`GiA0{85)Y;GfD7ilAeqh{S*gpmPH@m%3y zd(_e-G~Hb}nnk4pJ3+TeeChOC=B0%rF8S;{Nu9$eb)f>nLmz$ciy#ve$HZ>&L>Tlu zQ;RxXj@+~F;HG7GY{@!f>d6J`SfNK)gAoA0|BAY_{5&rz!kRMA#979@hee=}-v__0 zVK!IvJp$e_;?g=)%XjWWnm>gP=$`D)gvT;Uq|AF4i`TglY3dA|aXe%`&XrtXP3Xev zX&uu>E!CZO-pPIrwPWWn7HNl8$3EpNSoVRh0_1?UIB9+a+)n7HE-QDQL$JZ?%$pH! zkT&8LX8b$xepE{&3Wo6Gu1ixvX}n`ndCPSKk?;)<0Z)<^_+|qtY3r9IMI~*&DnRV3 zIGOPhPt9pLmhF2&%H((4Xr?2C2L&6+mU<|YOJ0~~;>=%pU#;J}!MM=Ftd7KUsl<EC zyB?wK?OPETu^dRR;B3}{Ld1N2>nNX)zFsVM@7*rDJI~AB-hSTOj;yQ#W?CS2_~iH` z@zPJ`ODJQxznqPtp03at4p(!Oi3Pa&$|byKrx%z!Fv010+1=mGy5QC+8+YgK?XtOb zt2}?YU!FestUP`8#jKMOeFZ}6xX%VVZbK}m36J*7jq8OlF>jsN0bu$B93sl`81?$D zUWSxo3VyeH;w^L*_mz16^Id`)vLIlju`9(~hpp8ilGrxu)wPgA@Aze%_NHnn4sMNa z6X~8?wF^OJ;4Oo9mZKJ}AkkGNbFgg=e2|A{3Bcc(ROZ?Fip&Fe$#Cr>c^5zkm0Xuz zI{z`i>R5XCFe_&k89#Rf@BZaqmP43EChlYkAXg(aSHvhhY^zIaH@^GbwEy6*|GJz& z)M}bx7L>EE8v`wN|J`@X9>T>X!21XfH9-Z4nxIQ7J?T?npm0#r4Wpu83NZyk|Ni|D zrJGGJfx#j1=8e_D`)HCp3n#T#iMzqSxd<Dvsns$-73Tc*9&J(v%s4u^?KkxuceVc< zU&8wcba2@WnHOOc^&|Vi9aP_P2l#pRR)6HDWnrL)SW?qT*}@`Gt3$`7Eiw$<K@-~M z6vA?>7uib>0d{qP(xaan!K9$*4G=Q4D`zml_^&R`a~7Br@_s04Ku+hL+&xY~Wk6mD z-aaba?R!(C>;qsetF%wc$PH@lwb2dcu@)Yf=e`Rqf+K`|#+>&uOd0Y8<t*rq_AU-E zCYRvAj;=@A<f<Cexj9cQf1mN!O4I_;I&}U^_vaFDx~2>TiEwu_s*58WU-8#Qdi2dQ z0w&)`C*Rs{|8`mb!4H(2Wgo%z{QdW%ba*IWE}1%IUD9_IibLWQ6epCW`_;4xJrxaC z5XuK)@$fE+1(*4<Nm2ZPHl1s*oi1icTH4aNJjc4JJJqGncn8QU{}C^Qr35QhktH}6 ziy?6YetVaaf{)yzt^110^1|!P$8TxQdi*W!diS%qv8V}AAPK_Fe3jqS(B93*TbDsv zNj-#$AJ7~9J2^Tn8*AI}pbhGxZtzaw5ne>yS}2b5_SQDau8I=xuC-LwvqN3w%Xwhi zr)!KX@kX7UxoQpNs52<bgApBto+#6mU&9f?W;7f>wlSJKuD&`xLYqPhbAbH5d87{w z^)4aWpu2bPm0L)==g)V_yYKugLPZnOjy1+^$6Mp8kf?yLvp&zK^SQk@#Jpr!j5F4x z^J!XML-KPsuEiSH2Dx2HXWZ;+X_U^^=hDu!m%r4>rD>;y60KtvSK~!92ppmh0#S|( zi9(}^TTT$1#3CRWOvFPH2iZhUk=y(tTkD6gihvQ;JexPGVfd{J!O1<Xjx3>+Ny98e z&;>LH^BuwX-7u&-0+qTPX|0e>*MqC8)QsJmaSNhedF{2hr1W(kbGf?16*4+2)l3gT zILG!_p+ej_OzNEcYO7bY$@jX*WH$PGWjNj?UJ2ed=<ZrvMJcgA#CwuxPEe(9ais_k z%2sehGn~O8t^w}C)Mw)whFoWIv;Gd;cu+7n3Gew>3id{5!Z22#?cz`O*ir{=40=33 z^KvhfN|Sp!+^BkTg!Vn5zqEY=0WzdK$4Zp+phXIRz|KN!(N>8MZA`C92gdFMyxME# zI8aQg^+Y=<!+2M-Ob(7Rk)NI92p{*)=!f=r68DpUK<TeSBsfVKwW6y`nrj`a1n9zQ zi#Ajqv{XHsMt^rKT5*Q_S`u2bE)QQpc!*EBwCtlRo%Ap`mb$h2@PG&o>QQNNKFmpQ z$5ZQ5*Op3+f@%kWAim24-QOfWI*;ZObZN%<yN&OCuP!IY%1{7gE)1M-dF~PQ>r%69 zl?&Uc6{K5JMMRuY$q=8i@su<&FZ(5Y`oL3bq^_KU24QGw#p0@rQ2Nr^^ynL@`Yisc z>MjoMP*&(vm}Zc`5Xv$x{DCsuD->mrv<7)bgbVK@@OfuG>uZeNcQy4aklr$U&#MG) z)?=LI8N!$HRLe+tEdxPZ2w$7kZ;&^O?=pQ?VBNcaH>ZlL2pe=A*Mk!k5cLp6^ZWcu ztVdm1mUVFGmk&|=u+WFg1L}f4$unMEDM#XKx1W`S9s}bMCr-+*tSnanV&`2SYyul% z;hNb8NBe0+Tf&<_yWD~9DUct0_<s5IuYV2kK?LM!oif;2e|t3n(&FrZp{4iMc9~b; zB$|8>&m4ebrTA>R#MCkxKwG(n`Da6G*-gA<kf#X{zfXZcCi(?4mkBte$&xm{(0m-U z6q9rnL7dRCuMULcm$FEae85jQ`o89`W^zT|iYPpheug-{r){=d2B@DGAjjS0ikM@x zM_o);FbtPyI<e`p-+cBIM9Rt~+d&@v*UoD!X??&9B<%G!-waWnzz9T?#&E7cjD1}S zGJ48&)0T573XU7UHRGt(m&s6t3;EP?U6LykcVZP}iW{%Zk)$2ulUeESZrW`&rOo;) z41{HySa!nS6-Uhy@qNZmp!3d&NhqEHfSC+>SZ=3Oi&g~O+KQkEb1tnEzw>edgK>PP z;FkL|wl-Y3ghdB|WW}ID<nR<z-~CNPF8YCX=9lCaB4p6ej$po%lUQ?}-*q(=AHhwS z7?ewY9CH;67zV=5ah3tu^i|rRP<1ZFzO7*4Y_4yX`&g6GU-t3PcKhnwNN|U|={rIu zFcF8u3$C2T>)0@UE~8aBP+4&PqoYzm845a=(kejIz(4=huVS5OH9lvoW#kd)mP!7s zmJ@EYUtyt@;kP$j2C6dRJe@13a-soo;+*ZX?OGtNz;Xkj<1j(s?ELNDmIJI+al*Yo zae0znmzDTpT?!Dma8*F4R|`r(;&bLX=%X~Gbs}7sf-;oB;Cry<FjnAIIfn40ABBg< z%|L8r)g|4;dv03rM_5w6c&8f+c}yUW@O7+sALdS4EnDHA^!&E&c4_^X)+;niaNeWk zHJ%sAXM3uP-YDuKt?$zwg7@I~ETp{l-b@7F8sF{_9(ki&yb7aV>@JkeO?df0SFT_C z>_=!Qw8cRMz99nEwqQ}X0&{<VFRKl^C|B#yg#6Hh8wZr*w?bmgL2E9oT1If_sff2` zxIBLB<oV|2HpSM4x=vny_6g?vDdx5eM#k5K#Dj+qvWn+ZwB4s5gzhg1rUArzfIfRR zuVFskyH|iowO<t=KAWS0hhMq3G`6y7Wvxy_ZGFABxG?;=_pSVv)`?av4L>`In7bMH zmWM9S5NW0j8qb}QEIitmh?u_FPk)ms>$!=mdC|y|C%L#PQ_=6O+3jFv{WUKaW=sA2 zElR3s=Da6}PwPQ9#W6%L!*HI&dSwnCT;~pRCpbfVFc2*p<D8JE;nAohL>X&@59wTD ztCgY)VFAISuXzuKp=%*l2lcz$Q`dsbKg%b%6dInhwLrV_6+oC((|8?k>y}xGM_%#E zQoobBGOV~p=$G1MtkM}=8-b+%l#yfLX`5C^CZTy3-!r%%oMbXCe_dV~gxLuXVHy_# zmt`4@k8d8$D)2dlT`pdh&9#l-z_q6NbdpK*ljHL+*!5wrEcM07iaSn#JJ;gJ&paz= zxLm&;d2)(CMAHq7Nyo4{!f{3PJew=Ha+Mwbt7v=*>n>%uv2Y!@z51<hm4|P<kt1I` zEYPtP&%`(T5`0RZ!5o;N?zAop!*V5TnOSy<)5i^6hWz&2GljHgPxVm_Jw#I%qi#oi z=8l>35DIhWMpQOvP~OSlwcup#3J;Y>$4Fshyh>*FZkUE=R}Shf)J3}f=uzf9v}#Ym zm!r==&-{rBhYFUi%qS4RH**ry+YUn%PpngccShQ{o9x@H?@>C4bIMX2m;ys-&oOpC z(<yM*A`uSBPrDVmqk*SrP%e2rwIE|@x#EbwYy;08k0UNYnxRj3kwZaIAR;ihnlcmS z&E4@fUM0_VNaw;RWzVkg2iwK}F7b|n;V=6|u$+`dy2eX<<L~y~(0+SQS_;iEQ4vg( z!KLe!9Ayow+J!ytG}r34&6XqMmtSi=Id6A<b_w3CLN6EDb2A#Q1!j)9pUibkoFne} zc?Ia?)z@B2`<)B4Ii2Ov^5gaOF<Q`C@p$UZtxb^S6qEK4e3EI_`4N#>B(h?y6{Xoz z%u)SpcG$~cBTegX?~Uoc{5+q>8_{zi;7eLBS^+W67(Z77hQZ%0otGts*k+-nyLoSZ zfuC3q4~@{E_Ue1{I<P?);;o&Z^_}_bha<;enXcu@!1K<;B9QUAIk+)6BIHbS0r7Nw z^%;~7$Rq;0k<IU6I7up#F`W$5<3L<t;RLRXU*b6d8N7>_)~65|z4A(!&hvlw@5&iS z;PPHgeeaI)34~0=VE__*HuVvw4|@kCV2?bSx-R$CeWfqliOX~9U2+nYxG$ZY1!-JV zp%77Hbn?^9gWE7bt*cDP+HaVaxT1^1XYaa9oM2beJ%Wii!K+Q%@Q430c1{edHOU}6 z%}C+l0tMsTeVx5E#JkE$==IUK&LQyraJeEp<>e@H%5jW?bKLW7Yha;^s7J&(%8~FM zt&Ss97CSQQ4nhWjh$eQpe^iz+-}{csq82t6<L*2kMP{+Y`5A4ce`g2~g_lcz7iN1? z+(+VA_E)re5E!)9-y2voXNM3aZS`ba$5`cM2gdFMrfHmmiedr`#48mCgSh9|>+Yht zWdPo|k|yV?**DS&9~WcDbY;Gq5Iiq3Ho6;C1hT1=_Uf8+9A&!VMuZ7vISvYDkH1ma zI=^OFDhH0UT5P-$c9eQtU8NG_^4(i+y%k(^!G(fDM&u#`g@kpgcxZWKB^G&{TM1tB zPT}G4?+SnOrB2$a;PJQP=loEgvA+O*_E&glr%x&C3ivCWrN7pP0b`xHE5-<!D;e-k z_hZG4O0U404Cb*d!reVV7nfR|y5NBcd{CTH2!-ZJ>phDr$Yc9t&erW%#Bw5DTDA^p zRhm_q6w|HC(0HQh66dq|xTFjF7kVe1zvu6{F62XYstUU4*+tylTHE8*H3Ykp?E7K* z(xe_2%MNW~QM;*C%V-r`_b4ile!m8Hr{JvLRo9i}r2E;~G)sT8Zw*-FtSwJ4Ute2) z?BuzoiN`hgk(QH_LozLaBy|tSO=G!46z58ukkNf*=j@O`@a|=3&-0gQ43^__`>hMf z_ugw%m9#`97}v^gd5p}HiCP??y*I64{&#!!?q%FENau<i2R`ree{F$jqP07?4F<Dv zs*s|Lq^{*hz!2~GN}80Zhmv$xL{s8-Z3j8IyMA9n_n3uzubInw)jGZ6V`Z?_WUbA$ z_Bqj3_!&nU3QU=<no{FCLG#nC5Nilzk$IdWG-OJiS)^-2CL^I`d8bT+{JIp}DgF!% z_&{Moel^nr%F2g(AP$LN_f;nOc~*<`oFdDU$)ugM$v%aUsDJE)EqT;hWd>RwmL;=L z)728G0D*A0w75LASopY;S)s87zP<6?@0L4QS+-F(iVVQ-NsmToM)VOtRl-24a?L-3 z5f}(7<AZa+Q2dpXIFY?F9EE8+ioHIthY)4Xx~KMbj%ND@utE?ZadV>WS&E<6JtP0= z<UA&R=3IbB7lINYuBk|$ymjRh&D1y0(k1Vj0tF&JpPYplZI_H%H>BhA082!dqfA=d ziWW;6`yufMCy1vxnFDbZmr905&1k8fGG1b`R>1h~-3acd2q#?_x{5M(yw~!Rp*JQi z19zOPGxKc3%eck!&;V_<kGdpP62v(zLSZkis$_cPjaG=(knf`LbGJU-V!E*00ITv~ zoGwrI7Mu$|(t5s|yXbqAp<-mc3|{srxj8Y1E)o~3#Qe4oMt@GL6=p|_z5S>j0GK-c z*&d>(@XkMrbL>>;glGCnS-KJ}PoPSxGGNn7!&%}FeTttdb<(5wBt2`5WQ=Hg#)#+q zwm!?`GWgE6_{;iQXo_yLIvQ^=)_2l_zoqe(Ch3Xu*)eh@slvf$an!W#gK`1K>e_l3 zy$gggwzK@UVR;!?F8eh@aD9Idyn^R2hQI#s{orlp1S!w?@aoEUo_v$I5{t(Dg1+nA zx^?@p6X-VH9qA{>M_KA+4!a`Kcv-o^LT%7Sxv|jAgDtd9wMGV@v)}X2?R^W4=^E<$ zT8_c!dJ#t%HLj*@_Y4xx_19N}rL=a<-xIj&B5{>XHh6(74jL`E88FkTv8sVojj(2I z18-XuKKVslGzG4_v(H8|2x^cLf8v2<c6u@rGD_RfkVV&$_i9Gh34YV+WA+LoB)vhH zrVlbt;;cJvD;PvhhlP=tXY<?N&RxHCgosR=zDZ~i9+V|<M>FJELE_m%XWV5tu9T6{ zC`|mVCfT$?HBv`KCMDBzGo%OTsR7I6J)}=!*0mL_m3%S^1(w>HkdnBCt$ouS<Atge zI0zpPZ@cs0L0klf2uSbdb!=QR>)~%Q8ZG5Eho?)H2;ee<lk*b%DV#DWXqH+HYa6Rs zk|^8<PJmA@s8{#YY1!XDMmY5mAcN9T`JKT&ljSqZQY%CYMW$nY9$AvF6H>*Z^Mcf+ zrJ?xuNO#NkQ?fJ%A?n#v)v}QIy=4CPT-d<f{S|8YVLr6apamj)q#0dj&J}qVCgO<< zK$nAa1|CDB=Ktl)W#xEh<0fMv0|vP=F2djP)tDOtQ?QBu))N827$~G{kFKg{*3>CX zY<Ir#K^jl<jfP6O=6AWTf+{B!Q?3?hmpGYeEDkPd&(jICI^yP{4uzkS@wPEa$ktZA zXQRTQ;_BVGH}PKPE`L8$kdR-x@zOF_gr-L;&=2o2?Dp04X3*qdT;vJjuryrvgF&~H zBi&L=RXDf{7X_ubmial-3xh`U;Vr~_uY88*Sw3s>s=}i(W`pnAJ+KB=hQ=>>k>C8t z@9@g}v@Y_foOtk}b<l3xS_Q>bmgkx-Un!)A;^_MNMmz}O%AGrRQ%^ok3#{bvw{G7m zAAR&O{G+a3Oq+mzcnjm=Z<q7xzS8>8Lg{R7ZawzH>^9!XL{}qCr!X84Q4LduW8pSZ z6Ud`Wv{pP5%fXB^P6q_hj9>}=ZkKT_O-uK3sM+eEnI_Vdj=!8QKqPLy%nH?dYr%59 zdan;Hj|@e+ce$kii-6#dnrd3p2rw=Swmyg7xD?7IBNZW%mftaPXU`6<unB^hmwegd zQFDaYU27et$z3fd!h@@wuq!(GaVd}5)=eOx#{?yhMCK%=cZHR8xB@5e;1XKR|E;EW z^!#}yLgU9KZJ0jkWFlG>aV2o6sX9*)zcjyPIudL41(5!R>2s%35h4v93JEnuHz%gw zBx%ctZL|5bT$;wKCTkiAQRENt`&+nkSKHpE|9;2fbFAH`;9UIkwwpG|G=^9Rc2?%> zj`0(fA0j-W#B<FY1D##YNlAWfZbhYFH1ssn9*o&}1r-2@5P{;v_|EQrtkbpiQS!yj zVLK5X!d4jS>J_#*l@}0rGVg*D@miANqwT{&akZ5lI>)kEjVrBF4i2yqdGC0KSqmFr z?A)Ssc7U)Hm#j-THyVh$ihDi|1zem*(!J+V`56!1tEroR2j(EWq5x2g!b7*A3Ph9u z>T_P9;dyY6`n6<KL=<8!6!04}@&MhR!E@4jmr0AK?k5s=6(YuY&Klqd^LG`OC+cc( z+6EVA*bbj<o9<ED<jTA(C&t2j`uXSO36`xcHxJ)*W2blD1vi0xKK$eu01KD*dp@G8 z*(^&sRB-u;gAN0sCq-ClwTfZlV_O6x-HXHG-UZ_)Jy<k9Lq|SjjJz}c!pXFO16P>q z42GsnJzUbeNMRa7gPBEIhV*KB$Jey&YUvH8X}qId)@Mk%^ka#e>1KSEcjCOb^7QGm za`*ndoZRbiJt}=F74}1(p|!1RS8GJrdL)YFIyGS*+W2Zs3NsZX-MwxYKR!7^;KGaU z-+%D<-dzvfsz(JoSRU;wvr^NO$ysQde0lTDH}hc=k1weJsSLAqHVll=ey3A*b}sn4 z0juUQ&T`uOI=GhA$~3(RE!})c%Dq-M8z_JK&gWTNCYShG>!=m9yHYtO^wfzcA{cO) zjkVYUfhM4VbC5=6=RH-LN{pQS3Q~~X7=^JynQ;vqEkgqIeH%Qr78!<v6fOJy{qpb| z-^iN&Rei~?yiy)wJt%07AUffnCX!zhyJ>;j<naVlm-yPz2qy9`ySY$zg2W1O#w;g~ zT3#3l&-%aJlOYkhG+4&mh+9D;qmlVUQ>AQu=W4HUJCWDrpeh<+rs50r95^9QIUf1$ z*hM2Itp_;9Z%&+uY+LM$7L;0!mYKxihOaDZr3^P_-t|OT-hKJeN9nUnN5-VJ<ms@Z zp<gDv3L{X@R4X<(;TM7A#cwSO$7TVBa)p3UkoetxyJ8ymYG_>y`@<m1{@x*0L$7SC z@f${OW-X6;(lqd*8j04Ozs`NQT1Ogkt-k}WuiKy^<YqMQe!*}6@%g>$!^3JRIp?Ed zp;oIf(0Yuv&v?t2Y+x7{{Zp~fBGBC>oMiHDAk0@Sfsu1rP4RQG+Odn9iu_uSSq(=$ z!YepH-O_@VO$fv`SU$g-RxLbjkNZsQbUstd<@agsyzxf44L{gG%XKa%jGH!DmTf$t zJ?#?$;+k`ETC^_4&@Fn*SneS-4!vl1+Dp4^n@Y<LOwBnP(>Y(G@UU#l8@MkASYJc9 zaV<i_3h*<=L_FysxJNKXm8LLv;^-*?hPH(V�KPoOiO1a2iqo1Ahy1@`)$rGo5#F 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 Ef&*k|!vFvP From 9cc6e83819ae7ae7b8ec3b5276a197592d9747f8 Mon Sep 17 00:00:00 2001 From: Vikas Dongre <zvikasdongre@gmail.com> Date: Sat, 17 Feb 2024 23:38:30 +0530 Subject: [PATCH 251/514] fix: installer showing feeding failed, as well as adding proper error handling. --- public/install/forms.php | 35 +++++++++++++++++------------------ public/install/functions.php | 22 +++++++++++++++------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/public/install/forms.php b/public/install/forms.php index 98f05657d..96f1dfa17 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -29,8 +29,7 @@ try { $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); - } - catch (mysqli_sql_exception $e) { + } catch (mysqli_sql_exception $e) { wh_log($e->getMessage(), 'error'); header('LOCATION: index.php?step=2&message=' . $e->getMessage()); exit(); @@ -69,26 +68,26 @@ wh_log('Feeding the Database', 'debug'); $logs = ''; - //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); - //$logs .= run_console('composer install --no-dev --optimize-autoloader'); - if (!str_contains(getenv('APP_KEY'), 'base64')) { - $logs .= run_console('php artisan key:generate --force'); - } else { - $logs .= "Key already exists. Skipping\n"; - } - $logs .= run_console('php artisan storage:link'); - $logs .= run_console('php artisan migrate --seed --force'); - $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); - $logs .= run_console('php artisan db:seed --class=PermissionsSeeder --force'); + try { + //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); + //$logs .= run_console('composer install --no-dev --optimize-autoloader'); + if (!str_contains(getenv('APP_KEY'), 'base64')) { + $logs .= run_console('php artisan key:generate --force'); + } else { + $logs .= "Key already exists. Skipping\n"; + } + $logs .= run_console('php artisan storage:link'); + $logs .= run_console('php artisan migrate --seed --force'); + $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); + $logs .= run_console('php artisan db:seed --class=PermissionsSeeder --force'); - wh_log($logs, 'debug'); + wh_log($logs, 'debug'); - if (str_contains(getenv('APP_KEY'), 'base64')) { wh_log('Feeding the Database successful', 'debug'); header('LOCATION: index.php?step=3'); - } else { - wh_log('Feeding the Database failed', 'debug'); - header('LOCATION: index.php?step=2.5&message=There was an error. Please check the installer.log file in /var/www/controlpanel/storage/logs !'); + } catch (\Throwable $th) { + wh_log('Feeding the Database failed', 'error'); + header("LOCATION: index.php?step=2.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); } } diff --git a/public/install/functions.php b/public/install/functions.php index 8eb1318cb..451d8b956 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -10,7 +10,7 @@ use Monolog\Logger; if (!file_exists('../../.env')) { - echo run_console('cp .env.example .env'); + echo run_console('cp .env.example .env'); } (new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); @@ -150,7 +150,8 @@ function checkExtensions(): array return $not_ok; } -function removeQuotes($string){ +function removeQuotes($string) +{ return str_replace('"', "", $string); } @@ -162,7 +163,7 @@ function removeQuotes($string){ */ function setenv($envKey, $envValue) { - $envFile = dirname(__FILE__, 3).'/.env'; + $envFile = dirname(__FILE__, 3) . '/.env'; $str = file_get_contents($envFile); $str .= "\n"; // In case the searched variable is in the last line without \n @@ -236,9 +237,16 @@ function run_console(string $command, array $descriptors = null, string $cwd = n $path = dirname(__FILE__, 3); $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); - - wh_log('command result: ' . stream_get_contents($pipes[1]), 'debug'); - return stream_get_contents($pipes[1]); + $output = stream_get_contents($pipes[1]); + $exit_code = proc_close($handle); + + if ($exit_code > 0) { + wh_log('command result: ' . $output, 'error'); + throw new Exception("There was an error after running command `$command`", $exit_code); + return $output; + } else { + return $output; + } } /** @@ -259,7 +267,7 @@ function wh_log(string $message, string $level = 'info', array $context = []): v switch (strtolower($level)) { case 'debug': // Only log debug messages if APP_DEBUG is true - if(getenv('APP_DEBUG') === false) return; + if (getenv('APP_DEBUG') === false) return; $log->debug($message, $context); break; case 'info': From bfb169e1917f4249ea55aa57c86342aa4a320a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=A2=C2=80MrWeez?= <arsenyplis2018@gmail.com> Date: Thu, 29 Feb 2024 01:29:31 +0300 Subject: [PATCH 252/514] Added support for php8.3 --- public/install/functions.php | 2 +- storage/app/public/logo.png | Bin 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 storage/app/public/logo.png diff --git a/public/install/functions.php b/public/install/functions.php index 451d8b956..a5f2b0454 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -19,7 +19,7 @@ $requirements = [ 'minPhp' => '8.1', - 'maxPhp' => '8.3', // This version is not supported + 'maxPhp' => '8.4', // This version is not supported 'mysql' => '5.7.22', ]; diff --git a/storage/app/public/logo.png b/storage/app/public/logo.png old mode 100644 new mode 100755 From afe3fd23c5d0156fd1a14325daf8238e2357b46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=A2=C2=80MrWeez?= <arsenyplis2018@gmail.com> Date: Thu, 29 Feb 2024 13:42:15 +0300 Subject: [PATCH 253/514] Repair docker image for PHP-FPM by Hyd3r1 --- docker/docker-compose.yml | 20 +++----------------- docker/php/Dockerfile | 13 +++++-------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 98eded4ac..56c9e24de 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -12,7 +12,7 @@ services: ports: - 80:80 volumes: - - "../:/var/www/html:delegated" + - "../:/var/www/html" depends_on: - php - mysql @@ -42,7 +42,7 @@ services: dockerfile: docker/php/Dockerfile container_name: controlpanel_php volumes: - - "../:/var/www/html:delegated" + - "../:/var/www/html" networks: - laravel @@ -61,19 +61,5 @@ services: networks: - laravel - redis: - image: "redis:alpine" - command: redis-server --requirepass sOmE_sEcUrE_pAsS - ports: - - "6379:6379" - volumes: - - $PWD/redis-data:/var/lib/redis - - $PWD/redis.conf:/usr/local/etc/redis/redis.conf - environment: - - REDIS_REPLICATION_MODE=master - networks: - - laravel - - volumes: - mysql: + mysql: \ No newline at end of file diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 65dfab874..4b8d91dd9 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -1,16 +1,14 @@ -FROM php:8.0-fpm-alpine3.13 +FROM php:8.1-fpm-buster +RUN apt-get update \ + && apt-get install -y build-essential zlib1g-dev default-mysql-client curl gnupg procps vim git unzip libzip-dev libpq-dev libicu-dev libonig-dev libpng-dev libjpeg-dev libfreetype6-dev -RUN apk update && apk upgrade -RUN apk add --no-cache --repository https://alpine.global.ssl.fastly.net/alpine/edge/community/ - -RUN apk add --no-cache curl-dev icu-dev libzip-dev -RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip +RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip gd bcmath ADD ./docker/php/www.conf /usr/local/etc/php-fpm.d/ RUN mkdir -p /var/www/html -RUN addgroup -g 1000 laravel && adduser -G laravel -g laravel -s /bin/sh -D laravel +RUN addgroup --gid 1000 laravel && adduser --ingroup laravel --uid 1000 --shell /bin/sh --disabled-password --gecos "" laravel RUN chown laravel:laravel /var/www/html WORKDIR /var/www/html @@ -18,4 +16,3 @@ WORKDIR /var/www/html USER laravel COPY --from=composer:latest /usr/bin/composer /usr/bin/composer - From 6f6517033e0a3a23e6e3b15e0138d57422e56743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=A2=C2=80MrWeez?= <arsenyplis2018@gmail.com> Date: Thu, 29 Feb 2024 14:15:58 +0300 Subject: [PATCH 254/514] Fixed old link and name --- themes/default/views/admin/overview/index.blade.php | 2 +- themes/default/views/information/privacy-content.blade.php | 4 ++-- themes/default/views/information/tos-content.blade.php | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/themes/default/views/admin/overview/index.blade.php b/themes/default/views/admin/overview/index.blade.php index e9c4debfe..09f07e93b 100644 --- a/themes/default/views/admin/overview/index.blade.php +++ b/themes/default/views/admin/overview/index.blade.php @@ -43,7 +43,7 @@ class="mr-2 fab fa-discord"></i> {{__('Support server')}}</a> class="mr-2 fas fa-link"></i> {{__('Documentation')}}</a> </div> <div class="col-md-3"> - <a href="https://github.com/ControlPanel-gg/dashboard" class="px-3 btn btn-dark btn-block"><i + <a href="https://github.com/Ctrlpanel-gg/panel" class="px-3 btn btn-dark btn-block"><i class="mr-2 fab fa-github"></i> {{__('Github')}}</a> </div> <div class="col-md-3"> diff --git a/themes/default/views/information/privacy-content.blade.php b/themes/default/views/information/privacy-content.blade.php index 5f465e829..9b1db4995 100644 --- a/themes/default/views/information/privacy-content.blade.php +++ b/themes/default/views/information/privacy-content.blade.php @@ -49,8 +49,8 @@ Service or from the Service infrastructure itself (for example, the duration of a page visit).</p> </li> <li> - <p><strong>Website</strong> refers to CtrlPanel, accessible from <a href="controlpanel" - rel="external nofollow noopener" target="_blank">controlpanel</a></p> + <p><strong>Website</strong> refers to CtrlPanel, accessible from <a href="ctrlpanel" + rel="external nofollow noopener" target="_blank">ctrlpanel</a></p> </li> <li> <p><strong>You</strong> means the individual accessing or using the Service, or the company, or other legal diff --git a/themes/default/views/information/tos-content.blade.php b/themes/default/views/information/tos-content.blade.php index 6dd7c1293..e9a42927f 100644 --- a/themes/default/views/information/tos-content.blade.php +++ b/themes/default/views/information/tos-content.blade.php @@ -146,6 +146,10 @@ <p>- PayPal</p> +<p>- Stripe</p> + +<p>- Mollie</p> + <p></p> <p>You agree to provide current, complete, and accurate purchase and account information for all purchases made via the Site. You further agree to promptly update account and payment information, including email address, payment method, and payment card expiration date, so that we can complete your transactions and contact you as needed. Sales tax will be added to the price of purchases as deemed required by us. We may change prices at any time. All payments shall beinU.S. dollars.</p> From 6a11341c0a4256a5e59d8280a756d80eda36fea0 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 14 Mar 2024 14:40:08 +0100 Subject: [PATCH 255/514] Fix roles notification --- app/Http/Controllers/Admin/UserController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 60a1120fb..b023d2328 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -316,7 +316,8 @@ public function notify(Request $request) ->line(new HtmlString($data['content'])); } $all = $data['all'] ?? false; - if(!$data["roles"]){ + $roles = $data['roles'] ?? false; + if(!$roles){ $users = $all ? User::all() : User::whereIn('id', $data['users'])->get(); } else{ $users = User::role($data["roles"])->get(); From 7f0d934ed5afe30de58e46e2397473a7efd87e71 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 12 Apr 2024 14:03:11 +0200 Subject: [PATCH 256/514] =?UTF-8?q?feat:=20=E2=9C=A8=20Create=20LegacySett?= =?UTF-8?q?ingsMigration=20to=20centralize=20migration=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Classes/LegacySettingsMigration.php | 58 +++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 app/Classes/LegacySettingsMigration.php diff --git a/app/Classes/LegacySettingsMigration.php b/app/Classes/LegacySettingsMigration.php new file mode 100644 index 000000000..3b6ccfafd --- /dev/null +++ b/app/Classes/LegacySettingsMigration.php @@ -0,0 +1,58 @@ +<?php + +namespace App\Classes; + +use Exception; +use Illuminate\Support\Facades\DB; +use Spatie\LaravelSettings\Migrations\SettingsMigration; + + +abstract class LegacySettingsMigration extends SettingsMigration +{ + public function getNewValue(string $name) + { + $new_value = DB::table('settings')->where([['group', '=', 'general'], ['name', '=', $name]])->get(['payload'])->first(); + + // Some keys returns '""' as a value. + if ($new_value->payload === '""') { + return null; + } + + // remove the quotes from the string + if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { + return substr($new_value->payload, 1, -1); + } + + return $new_value->payload; + } + + /** + * Get the old value from the settings_old table. + * @param string $key The key to get the value from table. + * @param int|string|bool|null $default The default value to return if the value is null. If value is not nullable, a default must be provided. + */ + public function getOldValue(string $key, int|string|bool|null $default = null) + { + $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); + + if (is_null($old_value->value)) { + return $default; + } + + switch ($old_value->type) { + case 'string': + case 'text': + // Edgecase: The value is a boolean, but it's stored as a string. + if ($old_value->value === "false" || $old_value->value === "true") { + return filter_var($old_value->value, FILTER_VALIDATE_BOOL); + } + return $old_value->value; + case 'boolean': + return filter_var($old_value->value, FILTER_VALIDATE_BOOL); + case 'integer': + return filter_var($old_value->value, FILTER_VALIDATE_INT); + default: + throw new Exception("Unknown type: {$old_value->type}"); + } + } +} From 48cce11ebc776fc71f3d32009fc6e01821b89d26 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 12 Apr 2024 14:04:26 +0200 Subject: [PATCH 257/514] =?UTF-8?q?fix:=20=F0=9F=9A=91=EF=B8=8F=20Use=20Le?= =?UTF-8?q?gacySettingsMigration=20&&=20mind=20nullable=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...3_02_01_164731_create_general_settings.php | 61 +++------------- ..._01_181334_create_pterodactyl_settings.php | 60 +++------------- ...2023_02_01_181453_create_mail_settings.php | 47 +----------- ...2023_02_01_181925_create_user_settings.php | 71 ++++--------------- ...23_02_01_181950_create_server_settings.php | 53 ++------------ ...3_02_01_182021_create_invoice_settings.php | 63 +++------------- ...3_02_01_182043_create_discord_settings.php | 59 +++------------ ...23_02_01_182108_create_locale_settings.php | 53 ++------------ ..._02_01_182135_create_referral_settings.php | 55 ++------------ ...3_02_01_182158_create_website_settings.php | 60 +++------------- ...23_02_04_181156_create_ticket_settings.php | 52 +------------- 11 files changed, 78 insertions(+), 556 deletions(-) diff --git a/database/settings/2023_02_01_164731_create_general_settings.php b/database/settings/2023_02_01_164731_create_general_settings.php index 5d1835907..5e7360dcf 100644 --- a/database/settings/2023_02_01_164731_create_general_settings.php +++ b/database/settings/2023_02_01_164731_create_general_settings.php @@ -1,9 +1,9 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateGeneralSettings extends SettingsMigration +class CreateGeneralSettings extends LegacySettingsMigration { public function up(): void { @@ -11,15 +11,15 @@ public function up(): void // Get the user-set configuration values from the old table. $this->migrator->add('general.store_enabled', true); - $this->migrator->add('general.credits_display_name', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME') : 'Credits'); + $this->migrator->add('general.credits_display_name', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME', 'Credits') : 'Credits'); $this->migrator->add('general.recaptcha_site_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SITE_KEY") : env('RECAPTCHA_SITE_KEY', '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI')); $this->migrator->add('general.recaptcha_secret_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SECRET_KEY") : env('RECAPTCHA_SECRET_KEY', '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe')); - $this->migrator->add('general.recaptcha_enabled', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:ENABLED") : true); - $this->migrator->add('general.phpmyadmin_url', $table_exists ? $this->getOldValue("SETTINGS::MISC:PHPMYADMIN:URL") : env('PHPMYADMIN_URL', '')); - $this->migrator->add('general.alert_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_ENABLED") : false); - $this->migrator->add('general.alert_type', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_TYPE") : 'dark'); - $this->migrator->add('general.alert_message', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_MESSAGE") : ''); - $this->migrator->add('general.theme', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:THEME") : 'default'); + $this->migrator->add('general.recaptcha_enabled', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:ENABLED", false) : false); + $this->migrator->add('general.phpmyadmin_url', $table_exists ? $this->getOldValue("SETTINGS::MISC:PHPMYADMIN:URL") : env('PHPMYADMIN_URL')); + $this->migrator->add('general.alert_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_ENABLED", false) : false); + $this->migrator->add('general.alert_type', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_TYPE", 'dark') : 'dark'); + $this->migrator->add('general.alert_message', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:ALERT_MESSAGE") : null); + $this->migrator->add('general.theme', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:THEME", 'default') : 'default'); } public function down(): void @@ -93,47 +93,4 @@ public function down(): void $this->migrator->delete('general.alert_message'); $this->migrator->delete('general.theme'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'general'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php index 9ab9d72f6..d8f65248a 100644 --- a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php +++ b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php @@ -1,21 +1,20 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreatePterodactylSettings extends SettingsMigration +class CreatePterodactylSettings extends LegacySettingsMigration { public function up(): void { $table_exists = DB::table('settings_old')->exists(); - // Get the user-set configuration values from the old table. - //$this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); - //$this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); - $this->migrator->add('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); - $this->migrator->add('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); - $this->migrator->add('pterodactyl.panel_url', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:URL') : env('PTERODACTYL_URL', '')); - $this->migrator->add('pterodactyl.per_page_limit', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT') : 200); + $this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN', '') : env('PTERODACTYL_TOKEN', '')); + $this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN', '') : ''); + // $this->migrator->add('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); + // $this->migrator->add('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); + $this->migrator->add('pterodactyl.panel_url', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:URL', '') : env('PTERODACTYL_URL', '')); + $this->migrator->add('pterodactyl.per_page_limit', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT', 200) : 200); } public function down(): void @@ -54,47 +53,4 @@ public function down(): void $this->migrator->delete('pterodactyl.panel_url'); $this->migrator->delete('pterodactyl.per_page_limit'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'pterodactyl'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_181453_create_mail_settings.php b/database/settings/2023_02_01_181453_create_mail_settings.php index 8437a61ae..42ab0f4b2 100644 --- a/database/settings/2023_02_01_181453_create_mail_settings.php +++ b/database/settings/2023_02_01_181453_create_mail_settings.php @@ -1,9 +1,9 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateMailSettings extends SettingsMigration +class CreateMailSettings extends LegacySettingsMigration { public function up(): void { @@ -83,47 +83,4 @@ public function down(): void $this->migrator->delete('mail.mail_from_name'); $this->migrator->delete('mail.mail_mailer'); } - - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'mail'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_181925_create_user_settings.php b/database/settings/2023_02_01_181925_create_user_settings.php index 524ef0b76..8c9e088bf 100644 --- a/database/settings/2023_02_01_181925_create_user_settings.php +++ b/database/settings/2023_02_01_181925_create_user_settings.php @@ -1,27 +1,27 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateUserSettings extends SettingsMigration +class CreateUserSettings extends LegacySettingsMigration { public function up(): void { $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('user.credits_reward_after_verify_discord', $table_exists ? $this->getOldValue('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD') : 250); - $this->migrator->add('user.credits_reward_after_verify_email', $table_exists ? $this->getOldValue('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL') : 250); - $this->migrator->add('user.force_discord_verification', $table_exists ? $this->getOldValue('SETTINGS::USER:FORCE_DISCORD_VERIFICATION') : false); - $this->migrator->add('user.force_email_verification', $table_exists ? $this->getOldValue('SETTINGS::USER:FORCE_EMAIL_VERIFICATION') : false); - $this->migrator->add('user.initial_credits', $table_exists ? $this->getOldValue('SETTINGS::USER:INITIAL_CREDITS') : 250); - $this->migrator->add('user.initial_server_limit', $table_exists ? $this->getOldValue('SETTINGS::USER:INITIAL_SERVER_LIMIT') : 1); - $this->migrator->add('user.min_credits_to_make_server', $table_exists ? $this->getOldValue('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER') : 50); - $this->migrator->add('user.server_limit_after_irl_purchase', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE') : 10); - $this->migrator->add('user.server_limit_after_verify_discord', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD') : 2); - $this->migrator->add('user.server_limit_after_verify_email', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL') : 2); - $this->migrator->add('user.register_ip_check', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:REGISTER_IP_CHECK") : true); - $this->migrator->add('user.creation_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:CREATION_OF_NEW_USERS") : true); + $this->migrator->add('user.credits_reward_after_verify_discord', $table_exists ? $this->getOldValue('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD', 250) : 250); + $this->migrator->add('user.credits_reward_after_verify_email', $table_exists ? $this->getOldValue('SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL', 250) : 250); + $this->migrator->add('user.force_discord_verification', $table_exists ? $this->getOldValue('SETTINGS::USER:FORCE_DISCORD_VERIFICATION', false) : false); + $this->migrator->add('user.force_email_verification', $table_exists ? $this->getOldValue('SETTINGS::USER:FORCE_EMAIL_VERIFICATION', false) : false); + $this->migrator->add('user.initial_credits', $table_exists ? $this->getOldValue('SETTINGS::USER:INITIAL_CREDITS', 250) : 250); + $this->migrator->add('user.initial_server_limit', $table_exists ? $this->getOldValue('SETTINGS::USER:INITIAL_SERVER_LIMIT', 1) : 1); + $this->migrator->add('user.min_credits_to_make_server', $table_exists ? $this->getOldValue('SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', 50) : 50); + $this->migrator->add('user.server_limit_after_irl_purchase', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE', 10) : 10); + $this->migrator->add('user.server_limit_after_verify_discord', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD', 2) : 2); + $this->migrator->add('user.server_limit_after_verify_email', $table_exists ? $this->getOldValue('SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL', 2) : 2); + $this->migrator->add('user.register_ip_check', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:REGISTER_IP_CHECK", true) : true); + $this->migrator->add('user.creation_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:CREATION_OF_NEW_USERS", true) : true); } public function down(): void @@ -117,47 +117,4 @@ public function down(): void $this->migrator->delete('user.register_ip_check'); $this->migrator->delete('user.creation_enabled'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'user'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_181950_create_server_settings.php b/database/settings/2023_02_01_181950_create_server_settings.php index 7198adcb6..de25c94f9 100644 --- a/database/settings/2023_02_01_181950_create_server_settings.php +++ b/database/settings/2023_02_01_181950_create_server_settings.php @@ -1,18 +1,18 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateServerSettings extends SettingsMigration +class CreateServerSettings extends LegacySettingsMigration { public function up(): void { $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('server.allocation_limit', $table_exists ? $this->getOldValue('SETTINGS::SERVER:ALLOCATION_LIMIT') : 200); - $this->migrator->add('server.creation_enabled', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS') : true); - $this->migrator->add('server.enable_upgrade', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:ENABLE_UPGRADE') : false); + $this->migrator->add('server.allocation_limit', $table_exists ? $this->getOldValue('SETTINGS::SERVER:ALLOCATION_LIMIT', 200) : 200); + $this->migrator->add('server.creation_enabled', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS', true) : true); + $this->migrator->add('server.enable_upgrade', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:ENABLE_UPGRADE', false) : false); } public function down(): void @@ -42,47 +42,4 @@ public function down(): void $this->migrator->delete('server.creation_enabled'); $this->migrator->delete('server.enable_upgrade'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'server'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_182021_create_invoice_settings.php b/database/settings/2023_02_01_182021_create_invoice_settings.php index 8569c3ff4..effc67d7e 100644 --- a/database/settings/2023_02_01_182021_create_invoice_settings.php +++ b/database/settings/2023_02_01_182021_create_invoice_settings.php @@ -1,23 +1,23 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateInvoiceSettings extends SettingsMigration +class CreateInvoiceSettings extends LegacySettingsMigration { public function up(): void { $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('invoice.company_address', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_ADDRESS') : ''); - $this->migrator->add('invoice.company_mail', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_MAIL') : ''); - $this->migrator->add('invoice.company_name', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_NAME') : ''); - $this->migrator->add('invoice.company_phone', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_PHONE') : ''); - $this->migrator->add('invoice.company_vat', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_VAT') : ''); - $this->migrator->add('invoice.company_website', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_WEBSITE') : ''); - $this->migrator->add('invoice.enabled', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:ENABLED') : false); - $this->migrator->add('invoice.prefix', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:PREFIX') : 'INV'); + $this->migrator->add('invoice.company_address', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_ADDRESS') : null); + $this->migrator->add('invoice.company_mail', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_MAIL') : null); + $this->migrator->add('invoice.company_name', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_NAME') : null); + $this->migrator->add('invoice.company_phone', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_PHONE') : null); + $this->migrator->add('invoice.company_vat', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_VAT') : null); + $this->migrator->add('invoice.company_website', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:COMPANY_WEBSITE') : null); + $this->migrator->add('invoice.enabled', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:ENABLED', false) : false); + $this->migrator->add('invoice.prefix', $table_exists ? $this->getOldValue('SETTINGS::INVOICE:PREFIX') : null); } public function down(): void @@ -82,47 +82,4 @@ public function down(): void $this->migrator->delete('invoice.enabled'); $this->migrator->delete('invoice.prefix'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'invoice'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_182043_create_discord_settings.php b/database/settings/2023_02_01_182043_create_discord_settings.php index 60e450b90..21ccc5b63 100644 --- a/database/settings/2023_02_01_182043_create_discord_settings.php +++ b/database/settings/2023_02_01_182043_create_discord_settings.php @@ -1,21 +1,21 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateDiscordSettings extends SettingsMigration +class CreateDiscordSettings extends LegacySettingsMigration { public function up(): void { $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('discord.bot_token', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:BOT_TOKEN') : ''); - $this->migrator->add('discord.client_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_ID') : ''); - $this->migrator->add('discord.client_secret', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_SECRET') : ''); - $this->migrator->add('discord.guild_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:GUILD_ID') : ''); - $this->migrator->add('discord.invite_url', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:INVITE_URL') : ''); - $this->migrator->add('discord.role_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:ROLE_ID') : ''); + $this->migrator->add('discord.bot_token', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:BOT_TOKEN') : null); + $this->migrator->add('discord.client_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_ID') : null); + $this->migrator->add('discord.client_secret', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:CLIENT_SECRET') : null); + $this->migrator->add('discord.guild_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:GUILD_ID') : null); + $this->migrator->add('discord.invite_url', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:INVITE_URL') : null); + $this->migrator->add('discord.role_id', $table_exists ? $this->getOldValue('SETTINGS::DISCORD:ROLE_ID') : null); } public function down(): void @@ -67,47 +67,4 @@ public function down(): void $this->migrator->delete('discord.invite_url'); $this->migrator->delete('discord.role_id'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'discord'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_182108_create_locale_settings.php b/database/settings/2023_02_01_182108_create_locale_settings.php index 014dd8fd4..13dbde9dd 100644 --- a/database/settings/2023_02_01_182108_create_locale_settings.php +++ b/database/settings/2023_02_01_182108_create_locale_settings.php @@ -1,9 +1,9 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateLocaleSettings extends SettingsMigration +class CreateLocaleSettings extends LegacySettingsMigration { public function up(): void { @@ -11,10 +11,10 @@ public function up(): void // Get the user-set configuration values from the old table. $this->migrator->add('locale.available', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:AVAILABLE') : ''); - $this->migrator->add('locale.clients_can_change', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:CLIENTS_CAN_CHANGE') : true); + $this->migrator->add('locale.clients_can_change', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:CLIENTS_CAN_CHANGE', true) : true); $this->migrator->add('locale.datatables', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:DATATABLES') : 'en-gb'); - $this->migrator->add('locale.default', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:DEFAULT') : 'en'); - $this->migrator->add('locale.dynamic', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:DYNAMIC') : false); + $this->migrator->add('locale.default', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:DEFAULT', 'en') : 'en'); + $this->migrator->add('locale.dynamic', $table_exists ? $this->getOldValue('SETTINGS::LOCALE:DYNAMIC', false) : false); } public function down(): void @@ -58,47 +58,4 @@ public function down(): void $this->migrator->delete('locale.default'); $this->migrator->delete('locale.dynamic'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'locale'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_182135_create_referral_settings.php b/database/settings/2023_02_01_182135_create_referral_settings.php index 5727557b1..839f0e28b 100644 --- a/database/settings/2023_02_01_182135_create_referral_settings.php +++ b/database/settings/2023_02_01_182135_create_referral_settings.php @@ -1,20 +1,20 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateReferralSettings extends SettingsMigration +class CreateReferralSettings extends LegacySettingsMigration { public function up(): void { $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('referral.always_give_commission', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION') : false); - $this->migrator->add('referral.enabled', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ENABLED') : false); + $this->migrator->add('referral.always_give_commission', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION', false) : false); + $this->migrator->add('referral.enabled', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::ENABLED', false) : false); $this->migrator->add('referral.reward', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL::REWARD') : 100); - $this->migrator->add('referral.mode', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL:MODE') : 'sign-up'); - $this->migrator->add('referral.percentage', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL:PERCENTAGE') : 100); + $this->migrator->add('referral.mode', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL:MODE', 'sign-up') : 'sign-up'); + $this->migrator->add('referral.percentage', $table_exists ? $this->getOldValue('SETTINGS::REFERRAL:PERCENTAGE', 100) : 100); } public function down(): void @@ -65,47 +65,4 @@ public function down(): void $this->migrator->delete('referral.mode'); $this->migrator->delete('referral.percentage'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'referral'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_01_182158_create_website_settings.php b/database/settings/2023_02_01_182158_create_website_settings.php index fd542ff33..2bbdd5b57 100644 --- a/database/settings/2023_02_01_182158_create_website_settings.php +++ b/database/settings/2023_02_01_182158_create_website_settings.php @@ -1,27 +1,28 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateWebsiteSettings extends SettingsMigration +class CreateWebsiteSettings extends LegacySettingsMigration { public function up(): void { $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('website.motd_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:MOTD_ENABLED") : true); + $this->migrator->add('website.motd_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:MOTD_ENABLED", true) : true); $this->migrator->add( 'website.motd_message', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:MOTD_MESSAGE") : "<h1 style='text-align: center;'><img style='display: block; margin-left: auto; margin-right: auto;' src='https://ctrlpanel.gg/img/controlpanel.png' alt=' width='200' height='200'><span style='font-size: 36pt;'>CtrlPanel.gg</span></h1> <p><span style='font-size: 18pt;'>Thank you for using our Software</span></p> <p><span style='font-size: 18pt;'>If you have any questions, make sure to join our <a href='https://discord.com/invite/4Y6HjD2uyU' target='_blank' rel='noopener'>Discord</a></span></p> - <p><span style='font-size: 10pt;'>(you can change this message in the <a href='admin/settings#system'>Settings</a> )</span></p>"); - $this->migrator->add('website.show_imprint', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_IMPRINT") : false); - $this->migrator->add('website.show_privacy', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_PRIVACY") : false); - $this->migrator->add('website.show_tos', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_TOS") : false); - $this->migrator->add('website.useful_links_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:USEFULLINKS_ENABLED") : true); + <p><span style='font-size: 10pt;'>(you can change this message in the <a href='admin/settings#system'>Settings</a> )</span></p>" + ); + $this->migrator->add('website.show_imprint', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_IMPRINT", false) : false); + $this->migrator->add('website.show_privacy', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_PRIVACY", false) : false); + $this->migrator->add('website.show_tos', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SHOW_TOS", false) : false); + $this->migrator->add('website.useful_links_enabled', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:USEFULLINKS_ENABLED", true) : true); $this->migrator->add('website.seo_title', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SEO_TITLE") : 'CtrlPanel.gg'); $this->migrator->add('website.seo_description', $table_exists ? $this->getOldValue("SETTINGS::SYSTEM:SEO_DESCRIPTION") : 'Billing software for Pterodactyl Panel.'); $this->migrator->add('website.enable_login_logo', true); @@ -96,47 +97,4 @@ public function down(): void $this->migrator->delete('website.seo_description'); $this->migrator->delete('website.enable_login_logo'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'website'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } diff --git a/database/settings/2023_02_04_181156_create_ticket_settings.php b/database/settings/2023_02_04_181156_create_ticket_settings.php index 1e71ad3a8..41eefe737 100644 --- a/database/settings/2023_02_04_181156_create_ticket_settings.php +++ b/database/settings/2023_02_04_181156_create_ticket_settings.php @@ -1,9 +1,9 @@ <?php -use Spatie\LaravelSettings\Migrations\SettingsMigration; +use App\Classes\LegacySettingsMigration; use Illuminate\Support\Facades\DB; -class CreateTicketSettings extends SettingsMigration +class CreateTicketSettings extends LegacySettingsMigration { public function up(): void { @@ -34,52 +34,4 @@ public function down(): void $this->migrator->delete('ticket.enabled'); $this->migrator->delete('ticket.notify'); } - - public function getNewValue(string $name) - { - $new_value = DB::table('settings')->where([['group', '=', 'ticket'], ['name', '=', $name]])->get(['payload'])->first(); - - // Some keys returns '""' as a value. - if ($new_value->payload === '""') { - return null; - } - - // remove the quotes from the string - if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { - return substr($new_value->payload, 1, -1); - } - - return $new_value->payload; - } - - public function getOldValue(string $key) - { - // Always get the first value of the key. - $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - - // Handle the old values to return without it being a string in all cases. - - if (is_null($old_value)) { - return ''; - } - if ($old_value->type === "string" || $old_value->type === "text") { - if (is_null($old_value->value)) { - return ''; - } - - - // Some values have the type string, but their values are boolean. - if ($old_value->value === "false" || $old_value->value === "true") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return $old_value->value; - } - - if ($old_value->type === "boolean") { - return filter_var($old_value->value, FILTER_VALIDATE_BOOL); - } - - return filter_var($old_value->value, FILTER_VALIDATE_INT); - } } From 330ea45c5b9b4e3cc16d765f1c204b70bf7db42d Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 12 Apr 2024 21:01:00 +0200 Subject: [PATCH 258/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Try=20catch=20for?= =?UTF-8?q?=20possible=20non=20available=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Classes/LegacySettingsMigration.php | 11 ++++++--- app/Http/Controllers/ServerController.php | 3 --- app/Providers/AppServiceProvider.php | 28 ++++++++++++----------- config/settings.php | 2 +- routes/web.php | 8 ++++--- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/app/Classes/LegacySettingsMigration.php b/app/Classes/LegacySettingsMigration.php index 3b6ccfafd..dd32a7613 100644 --- a/app/Classes/LegacySettingsMigration.php +++ b/app/Classes/LegacySettingsMigration.php @@ -9,15 +9,20 @@ abstract class LegacySettingsMigration extends SettingsMigration { - public function getNewValue(string $name) + public function getNewValue(string $name, string $group) { - $new_value = DB::table('settings')->where([['group', '=', 'general'], ['name', '=', $name]])->get(['payload'])->first(); + $new_value = DB::table('settings')->where([['group', '=', $group], ['name', '=', $name]])->get(['payload'])->first(); + + if (is_null($new_value) || is_null($new_value->payload)) { + return null; + } // Some keys returns '""' as a value. if ($new_value->payload === '""') { return null; } + // remove the quotes from the string if (substr($new_value->payload, 0, 1) === '"' && substr($new_value->payload, -1) === '"') { return substr($new_value->payload, 1, -1); @@ -35,7 +40,7 @@ public function getOldValue(string $key, int|string|bool|null $default = null) { $old_value = DB::table('settings_old')->where('key', '=', $key)->get(['value', 'type'])->first(); - if (is_null($old_value->value)) { + if (is_null($old_value) || is_null($old_value->value)) { return $default; } diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index d291e2986..0c18a5084 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -8,8 +8,6 @@ use App\Models\Pterodactyl\Node; use App\Models\Product; use App\Models\Server; -use App\Models\User; -use App\Models\Settings; use App\Notifications\ServerCreationError; use Carbon\Carbon; use App\Settings\UserSettings; @@ -18,7 +16,6 @@ use App\Classes\PterodactylClient; use App\Settings\GeneralSettings; use Exception; -use GuzzleHttp\Promise\Create; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Client\Response; use Illuminate\Http\RedirectResponse; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index fb6e8f9ce..5e1f05d05 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,6 @@ namespace App\Providers; -use App\Extensions\PaymentGateways\PayPal\PayPalSettings; use App\Models\UsefulLink; use App\Settings\GeneralSettings; use App\Settings\MailSettings; @@ -89,20 +88,23 @@ public function boot() Log::error("Couldnt find useful_links. Probably the installation is not completet. " . $e); } - $generalSettings = $this->app->make(GeneralSettings::class); - if (!file_exists(base_path('themes') . "/" . $generalSettings->theme)) { - $generalSettings->theme = "default"; - } - - if ($generalSettings->theme && $generalSettings->theme !== config('theme.active')) { - Theme::set($generalSettings->theme, "default"); - } else { - Theme::set("default", "default"); - } + try { + $generalSettings = $this->app->make(GeneralSettings::class); + if (!file_exists(base_path('themes') . "/" . $generalSettings->theme)) { + $generalSettings->theme = "default"; + } - $settings = $this->app->make(MailSettings::class); - $settings->setConfig(); + if ($generalSettings->theme && $generalSettings->theme !== config('theme.active')) { + Theme::set($generalSettings->theme, "default"); + } else { + Theme::set("default", "default"); + } + $settings = $this->app->make(MailSettings::class); + $settings->setConfig(); + } catch (Exception $e) { + Log::error("Couldnt load Settings. Probably the installation is not completet. " . $e); + } } } diff --git a/config/settings.php b/config/settings.php index 6b6ae4b72..e9a3a4b10 100644 --- a/config/settings.php +++ b/config/settings.php @@ -32,7 +32,7 @@ UserSettings::class, WebsiteSettings::class, TicketSettings::class, - CouponSettings::class, + CouponSettings::class, ], /* diff --git a/routes/web.php b/routes/web.php index a6a2ab3cc..42d2edf37 100644 --- a/routes/web.php +++ b/routes/web.php @@ -77,11 +77,13 @@ Route::patch('/servers/cancel/{server}', [ServerController::class, 'cancel'])->name('servers.cancel'); Route::resource('servers', ServerController::class); - if (config('app.key')) { + try { $serverSettings = app(App\Settings\ServerSettings::class); - if ($serverSettings->enable_upgrade) { - Route::post('servers/{server}/upgrade', [ServerController::class, 'upgrade'])->name('servers.upgrade'); + if ($serverSettings->creation_enabled) { + Route::resource('servers', ServerController::class); } + } catch (Exception $e) { + // Do nothing if the settings are not available. } Route::post('profile/selfdestruct', [ProfileController::class, 'selfDestroyUser'])->name('profile.selfDestroyUser'); From 38cf81d92d4d418599514d13bdf4f0f3bd290ed1 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Fri, 12 Apr 2024 21:01:49 +0200 Subject: [PATCH 259/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Use=20groups=20on?= =?UTF-8?q?=20getNewValue=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...08_094402_update_user_credits_datatype.php | 2 +- ...3_02_01_164731_create_general_settings.php | 43 ++++++++------- ..._01_181334_create_pterodactyl_settings.php | 20 ++++--- ...2023_02_01_181453_create_mail_settings.php | 36 +++++++------ ...2023_02_01_181925_create_user_settings.php | 52 ++++++++++--------- ...23_02_01_181950_create_server_settings.php | 16 +++--- ...3_02_01_182021_create_invoice_settings.php | 36 +++++++------ ...3_02_01_182043_create_discord_settings.php | 28 +++++----- ...23_02_01_182108_create_locale_settings.php | 24 +++++---- ..._02_01_182135_create_referral_settings.php | 28 +++++----- ...3_02_01_182158_create_website_settings.php | 40 +++++++------- ...23_02_04_181156_create_ticket_settings.php | 12 +++-- ..._delete_notify_add_ticket_information.php} | 6 +++ 13 files changed, 196 insertions(+), 147 deletions(-) rename database/settings/{2023_05_07_195343_ticket_information.php => 2023_05_07_195343_delete_notify_add_ticket_information.php} (70%) diff --git a/database/migrations/2023_05_08_094402_update_user_credits_datatype.php b/database/migrations/2023_05_08_094402_update_user_credits_datatype.php index 292102ccc..db0acb2f1 100644 --- a/database/migrations/2023_05_08_094402_update_user_credits_datatype.php +++ b/database/migrations/2023_05_08_094402_update_user_credits_datatype.php @@ -26,7 +26,7 @@ public function up() public function down() { Schema::table('users', function (Blueprint $table) { - $table->decimal('price', ['11', '2'])->change(); + $table->decimal('credits', ['11', '2'])->change(); }); } } diff --git a/database/settings/2023_02_01_164731_create_general_settings.php b/database/settings/2023_02_01_164731_create_general_settings.php index 5e7360dcf..06c876c5b 100644 --- a/database/settings/2023_02_01_164731_create_general_settings.php +++ b/database/settings/2023_02_01_164731_create_general_settings.php @@ -27,70 +27,73 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME', - 'value' => $this->getNewValue('credits_display_name'), + 'value' => $this->getNewValue('credits_display_name', 'general'), 'type' => 'string', 'description' => 'The name of the credits on the panel.' ], [ 'key' => 'SETTINGS::SYSTEM:ALERT_ENABLED', - 'value' => $this->getNewValue('alert_enabled'), + 'value' => $this->getNewValue('alert_enabled', 'general'), 'type' => 'boolean', 'description' => 'Enable the alert at the top of the panel.' ], [ 'key' => 'SETTINGS::SYSTEM:ALERT_TYPE', - 'value' => $this->getNewValue('alert_type'), + 'value' => $this->getNewValue('alert_type', 'general'), 'type' => 'string', 'description' => 'The type of alert to display.' ], [ 'key' => 'SETTINGS::SYSTEM:ALERT_MESSAGE', - 'value' => $this->getNewValue('alert_message'), + 'value' => $this->getNewValue('alert_message', 'general'), 'type' => 'text', 'description' => 'The message to display in the alert.' ], [ 'key' => 'SETTINGS::SYSTEM:THEME', - 'value' => $this->getNewValue('theme'), + 'value' => $this->getNewValue('theme', 'general'), 'type' => 'string', 'description' => 'The theme to use for the panel.' ], [ 'key' => 'SETTINGS::RECAPTCHA:SITE_KEY', - 'value' => $this->getNewValue('recaptcha_site_key'), + 'value' => $this->getNewValue('recaptcha_site_key', 'general'), 'type' => 'string', 'description' => 'The site key for reCAPTCHA.' ], [ 'key' => 'SETTINGS::RECAPTCHA:SECRET_KEY', - 'value' => $this->getNewValue('recaptcha_secret_key'), + 'value' => $this->getNewValue('recaptcha_secret_key', 'general'), 'type' => 'string', 'description' => 'The secret key for reCAPTCHA.' ], [ 'key' => 'SETTINGS::RECAPTCHA:ENABLED', - 'value' => $this->getNewValue('recaptcha_enabled'), + 'value' => $this->getNewValue('recaptcha_enabled', 'general'), 'type' => 'boolean', 'description' => 'Enable reCAPTCHA on the panel.' ], [ 'key' => 'SETTINGS::MISC:PHPMYADMIN:URL', - 'value' => $this->getNewValue('phpmyadmin_url'), + 'value' => $this->getNewValue('phpmyadmin_url', 'general'), 'type' => 'string', 'description' => 'The URL to your phpMyAdmin installation.' ], ]); - - $this->migrator->delete('general.store_enabled'); - $this->migrator->delete('general.credits_display_name'); - $this->migrator->delete('general.recaptcha_site_key'); - $this->migrator->delete('general.recaptcha_secret_key'); - $this->migrator->delete('general.recaptcha_enabled'); - $this->migrator->delete('general.phpmyadmin_url'); - $this->migrator->delete('general.alert_enabled'); - $this->migrator->delete('general.alert_type'); - $this->migrator->delete('general.alert_message'); - $this->migrator->delete('general.theme'); + try { + $this->migrator->delete('general.store_enabled'); + $this->migrator->delete('general.credits_display_name'); + $this->migrator->delete('general.recaptcha_site_key'); + $this->migrator->delete('general.recaptcha_secret_key'); + $this->migrator->delete('general.recaptcha_enabled'); + $this->migrator->delete('general.phpmyadmin_url'); + $this->migrator->delete('general.alert_enabled'); + $this->migrator->delete('general.alert_type'); + $this->migrator->delete('general.alert_message'); + $this->migrator->delete('general.theme'); + } catch (Exception $e) { + // Do nothing + } } } diff --git a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php index d8f65248a..1bf0c7c08 100644 --- a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php +++ b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php @@ -24,33 +24,37 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::SYSTEM:PTERODACTYL:TOKEN', - 'value' => $this->getNewValue('admin_token'), + 'value' => $this->getNewValue('admin_token', 'pterodactyl'), 'type' => 'string', 'description' => 'The admin token for the Pterodactyl panel.', ], [ 'key' => 'SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN', - 'value' => $this->getNewValue('user_token'), + 'value' => $this->getNewValue('user_token', 'pterodactyl'), 'type' => 'string', 'description' => 'The user token for the Pterodactyl panel.', ], [ 'key' => 'SETTINGS::SYSTEM:PTERODACTYL:URL', - 'value' => $this->getNewValue('panel_url'), + 'value' => $this->getNewValue('panel_url', 'pterodactyl'), 'type' => 'string', 'description' => 'The URL for the Pterodactyl panel.', ], [ 'key' => 'SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT', - 'value' => $this->getNewValue('per_page_limit'), + 'value' => $this->getNewValue('per_page_limit', 'pterodactyl'), 'type' => 'integer', 'description' => 'The number of servers to show per page.', ], ]); - $this->migrator->delete('pterodactyl.admin_token'); - $this->migrator->delete('pterodactyl.user_token'); - $this->migrator->delete('pterodactyl.panel_url'); - $this->migrator->delete('pterodactyl.per_page_limit'); + try { + $this->migrator->delete('pterodactyl.admin_token'); + $this->migrator->delete('pterodactyl.user_token'); + $this->migrator->delete('pterodactyl.panel_url'); + $this->migrator->delete('pterodactyl.per_page_limit'); + } catch (Exception $e) { + echo 'Caught exception: ', $e->getMessage(), "\n"; + } } } diff --git a/database/settings/2023_02_01_181453_create_mail_settings.php b/database/settings/2023_02_01_181453_create_mail_settings.php index 42ab0f4b2..b7d2c0d38 100644 --- a/database/settings/2023_02_01_181453_create_mail_settings.php +++ b/database/settings/2023_02_01_181453_create_mail_settings.php @@ -25,62 +25,66 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::MAIL:HOST', - 'value' => $this->getNewValue('mail_host'), + 'value' => $this->getNewValue('mail_host', 'mail'), 'type' => 'string', 'description' => 'The host of the mail server.', ], [ 'key' => 'SETTINGS::MAIL:PORT', - 'value' => $this->getNewValue('mail_port'), + 'value' => $this->getNewValue('mail_port', 'mail'), 'type' => 'integer', 'description' => 'The port of the mail server.', ], [ 'key' => 'SETTINGS::MAIL:USERNAME', - 'value' => $this->getNewValue('mail_username'), + 'value' => $this->getNewValue('mail_username', 'mail'), 'type' => 'string', 'description' => 'The username of the mail server.', ], [ 'key' => 'SETTINGS::MAIL:PASSWORD', - 'value' => $this->getNewValue('mail_password'), + 'value' => $this->getNewValue('mail_password', 'mail'), 'type' => 'string', 'description' => 'The password of the mail server.', ], [ 'key' => 'SETTINGS::MAIL:ENCRYPTION', - 'value' => $this->getNewValue('mail_encryption'), + 'value' => $this->getNewValue('mail_encryption', 'mail'), 'type' => 'string', 'description' => 'The encryption of the mail server.', ], [ 'key' => 'SETTINGS::MAIL:FROM_ADDRESS', - 'value' => $this->getNewValue('mail_from_address'), + 'value' => $this->getNewValue('mail_from_address', 'mail'), 'type' => 'string', 'description' => 'The from address of the mail server.', ], [ 'key' => 'SETTINGS::MAIL:FROM_NAME', - 'value' => $this->getNewValue('mail_from_name'), + 'value' => $this->getNewValue('mail_from_name', 'mail'), 'type' => 'string', 'description' => 'The from name of the mail server.', ], [ 'key' => 'SETTINGS::MAIL:MAILER', - 'value' => $this->getNewValue('mail_mailer'), + 'value' => $this->getNewValue('mail_mailer', 'mail'), 'type' => 'string', 'description' => 'The mailer of the mail server.', ], ]); - $this->migrator->delete('mail.mail_host'); - $this->migrator->delete('mail.mail_port'); - $this->migrator->delete('mail.mail_username'); - $this->migrator->delete('mail.mail_password'); - $this->migrator->delete('mail.mail_encryption'); - $this->migrator->delete('mail.mail_from_address'); - $this->migrator->delete('mail.mail_from_name'); - $this->migrator->delete('mail.mail_mailer'); + try { + $this->migrator->delete('mail.mail_host'); + $this->migrator->delete('mail.mail_port'); + $this->migrator->delete('mail.mail_username'); + $this->migrator->delete('mail.mail_password'); + $this->migrator->delete('mail.mail_encryption'); + $this->migrator->delete('mail.mail_from_address'); + $this->migrator->delete('mail.mail_from_name'); + $this->migrator->delete('mail.mail_mailer'); + } catch (Exception $e) { + // + } } } diff --git a/database/settings/2023_02_01_181925_create_user_settings.php b/database/settings/2023_02_01_181925_create_user_settings.php index 8c9e088bf..683c998b0 100644 --- a/database/settings/2023_02_01_181925_create_user_settings.php +++ b/database/settings/2023_02_01_181925_create_user_settings.php @@ -29,92 +29,96 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_DISCORD', - 'value' => $this->getNewValue('credits_reward_after_verify_discord'), + 'value' => $this->getNewValue('credits_reward_after_verify_discord', 'user'), 'type' => 'integer', 'description' => 'The amount of credits that the user will receive after verifying their Discord account.', ], [ 'key' => 'SETTINGS::USER:CREDITS_REWARD_AFTER_VERIFY_EMAIL', - 'value' => $this->getNewValue('credits_reward_after_verify_email'), + 'value' => $this->getNewValue('credits_reward_after_verify_email', 'user'), 'type' => 'integer', 'description' => 'The amount of credits that the user will receive after verifying their email.', ], [ 'key' => 'SETTINGS::USER:FORCE_DISCORD_VERIFICATION', - 'value' => $this->getNewValue('force_discord_verification'), + 'value' => $this->getNewValue('force_discord_verification', 'user'), 'type' => 'boolean', 'description' => 'If the user must verify their Discord account to use the panel.', ], [ 'key' => 'SETTINGS::USER:FORCE_EMAIL_VERIFICATION', - 'value' => $this->getNewValue('force_email_verification'), + 'value' => $this->getNewValue('force_email_verification', 'user'), 'type' => 'boolean', 'description' => 'If the user must verify their email to use the panel.', ], [ 'key' => 'SETTINGS::USER:INITIAL_CREDITS', - 'value' => $this->getNewValue('initial_credits'), + 'value' => $this->getNewValue('initial_credits', 'user'), 'type' => 'integer', 'description' => 'The amount of credits that the user will receive when they register.', ], [ 'key' => 'SETTINGS::USER:INITIAL_SERVER_LIMIT', - 'value' => $this->getNewValue('initial_server_limit'), + 'value' => $this->getNewValue('initial_server_limit', 'user'), 'type' => 'integer', 'description' => 'The amount of servers that the user will be able to create when they register.', ], [ 'key' => 'SETTINGS::USER:MINIMUM_REQUIRED_CREDITS_TO_MAKE_SERVER', - 'value' => $this->getNewValue('min_credits_to_make_server'), + 'value' => $this->getNewValue('min_credits_to_make_server', 'user'), 'type' => 'integer', 'description' => 'The minimum amount of credits that the user must have to create a server.', ], [ 'key' => 'SETTINGS::USER:SERVER_LIMIT_AFTER_IRL_PURCHASE', - 'value' => $this->getNewValue('server_limit_after_irl_purchase'), + 'value' => $this->getNewValue('server_limit_after_irl_purchase', 'user'), 'type' => 'integer', 'description' => 'The amount of servers that the user will be able to create after making a real purchase.', ], [ 'key' => 'SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_DISCORD', - 'value' => $this->getNewValue('server_limit_after_verify_discord'), + 'value' => $this->getNewValue('server_limit_after_verify_discord', 'user'), 'type' => 'integer', 'description' => 'The amount of servers that the user will be able to create after verifying their Discord account.', ], [ 'key' => 'SETTINGS::USER:SERVER_LIMIT_REWARD_AFTER_VERIFY_EMAIL', - 'value' => $this->getNewValue('server_limit_after_verify_email'), + 'value' => $this->getNewValue('server_limit_after_verify_email', 'user'), 'type' => 'integer', 'description' => 'The amount of servers that the user will be able to create after verifying their email.', ], [ 'key' => 'SETTINGS::SYSTEM:REGISTER_IP_CHECK', - 'value' => $this->getNewValue('register_ip_check'), + 'value' => $this->getNewValue('register_ip_check', 'user'), 'type' => 'boolean', 'description' => 'If the user must verify their IP address to register.', ], [ 'key' => 'SETTINGS::SYSTEM:CREATION_OF_NEW_USERS', - 'value' => $this->getNewValue('creation_enabled'), + 'value' => $this->getNewValue('creation_enabled', 'user'), 'type' => 'boolean', 'description' => 'If the user can register.', ], ]); - $this->migrator->delete('user.credits_reward_after_verify_discord'); - $this->migrator->delete('user.credits_reward_after_verify_email'); - $this->migrator->delete('user.force_discord_verification'); - $this->migrator->delete('user.force_email_verification'); - $this->migrator->delete('user.initial_credits'); - $this->migrator->delete('user.initial_server_limit'); - $this->migrator->delete('user.min_credits_to_make_server'); - $this->migrator->delete('user.server_limit_after_irl_purchase'); - $this->migrator->delete('user.server_limit_after_verify_discord'); - $this->migrator->delete('user.server_limit_after_verify_email'); - $this->migrator->delete('user.register_ip_check'); - $this->migrator->delete('user.creation_enabled'); + try { + $this->migrator->delete('user.credits_reward_after_verify_discord'); + $this->migrator->delete('user.credits_reward_after_verify_email'); + $this->migrator->delete('user.force_discord_verification'); + $this->migrator->delete('user.force_email_verification'); + $this->migrator->delete('user.initial_credits'); + $this->migrator->delete('user.initial_server_limit'); + $this->migrator->delete('user.min_credits_to_make_server'); + $this->migrator->delete('user.server_limit_after_irl_purchase'); + $this->migrator->delete('user.server_limit_after_verify_discord'); + $this->migrator->delete('user.server_limit_after_verify_email'); + $this->migrator->delete('user.register_ip_check'); + $this->migrator->delete('user.creation_enabled'); + } catch (Exception $e) { + // Do nothing + } } } diff --git a/database/settings/2023_02_01_181950_create_server_settings.php b/database/settings/2023_02_01_181950_create_server_settings.php index de25c94f9..bd63d8e71 100644 --- a/database/settings/2023_02_01_181950_create_server_settings.php +++ b/database/settings/2023_02_01_181950_create_server_settings.php @@ -20,26 +20,30 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::SERVER:ALLOCATION_LIMIT', - 'value' => $this->getNewValue('allocation_limit'), + 'value' => $this->getNewValue('allocation_limit', 'server'), 'type' => 'integer', 'description' => 'The number of servers to show per page.', ], [ 'key' => 'SETTINGS::SYSTEM:CREATION_OF_NEW_SERVERS', - 'value' => $this->getNewValue('creation_enabled'), + 'value' => $this->getNewValue('creation_enabled', 'server'), 'type' => 'boolean', 'description' => 'Whether or not users can create new servers.', ], [ 'key' => 'SETTINGS::SYSTEM:ENABLE_UPGRADE', - 'value' => $this->getNewValue('enable_upgrade'), + 'value' => $this->getNewValue('enable_upgrade', 'server'), 'type' => 'boolean', 'description' => 'Whether or not users can upgrade their servers.', ], ]); - $this->migrator->delete('server.allocation_limit'); - $this->migrator->delete('server.creation_enabled'); - $this->migrator->delete('server.enable_upgrade'); + try { + $this->migrator->delete('server.allocation_limit'); + $this->migrator->delete('server.creation_enabled'); + $this->migrator->delete('server.enable_upgrade'); + } catch (Exception $e) { + // Do nothing + } } } diff --git a/database/settings/2023_02_01_182021_create_invoice_settings.php b/database/settings/2023_02_01_182021_create_invoice_settings.php index effc67d7e..d3ac8f637 100644 --- a/database/settings/2023_02_01_182021_create_invoice_settings.php +++ b/database/settings/2023_02_01_182021_create_invoice_settings.php @@ -25,61 +25,65 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::INVOICE:COMPANY_ADDRESS', - 'value' => $this->getNewValue('company_address'), + 'value' => $this->getNewValue('company_address', 'invoice'), 'type' => 'string', 'description' => 'The address of the company.', ], [ 'key' => 'SETTINGS::INVOICE:COMPANY_MAIL', - 'value' => $this->getNewValue('company_mail'), + 'value' => $this->getNewValue('company_mail', 'invoice'), 'type' => 'string', 'description' => 'The email address of the company.', ], [ 'key' => 'SETTINGS::INVOICE:COMPANY_NAME', - 'value' => $this->getNewValue('company_name'), + 'value' => $this->getNewValue('company_name', 'invoice'), 'type' => 'string', 'description' => 'The name of the company.', ], [ 'key' => 'SETTINGS::INVOICE:COMPANY_PHONE', - 'value' => $this->getNewValue('company_phone'), + 'value' => $this->getNewValue('company_phone', 'invoice'), 'type' => 'string', 'description' => 'The phone number of the company.', ], [ 'key' => 'SETTINGS::INVOICE:COMPANY_VAT', - 'value' => $this->getNewValue('company_vat'), + 'value' => $this->getNewValue('company_vat', 'invoice'), 'type' => 'string', 'description' => 'The VAT number of the company.', ], [ 'key' => 'SETTINGS::INVOICE:COMPANY_WEBSITE', - 'value' => $this->getNewValue('company_website'), + 'value' => $this->getNewValue('company_website', 'invoice'), 'type' => 'string', 'description' => 'The website of the company.', ], [ 'key' => 'SETTINGS::INVOICE:ENABLED', - 'value' => $this->getNewValue('enabled'), + 'value' => $this->getNewValue('enabled', 'invoice'), 'type' => 'boolean', 'description' => 'Enable or disable the invoice system.', ], [ 'key' => 'SETTINGS::INVOICE:PREFIX', - 'value' => $this->getNewValue('prefix'), + 'value' => $this->getNewValue('prefix', 'invoice'), 'type' => 'string', 'description' => 'The prefix of the invoice.', ], ]); - $this->migrator->delete('invoice.company_address'); - $this->migrator->delete('invoice.company_mail'); - $this->migrator->delete('invoice.company_name'); - $this->migrator->delete('invoice.company_phone'); - $this->migrator->delete('invoice.company_vat'); - $this->migrator->delete('invoice.company_website'); - $this->migrator->delete('invoice.enabled'); - $this->migrator->delete('invoice.prefix'); + try { + $this->migrator->delete('invoice.company_address'); + $this->migrator->delete('invoice.company_mail'); + $this->migrator->delete('invoice.company_name'); + $this->migrator->delete('invoice.company_phone'); + $this->migrator->delete('invoice.company_vat'); + $this->migrator->delete('invoice.company_website'); + $this->migrator->delete('invoice.enabled'); + $this->migrator->delete('invoice.prefix'); + } catch (Exception $e) { + // Do nothing + } } } diff --git a/database/settings/2023_02_01_182043_create_discord_settings.php b/database/settings/2023_02_01_182043_create_discord_settings.php index 21ccc5b63..c8216c495 100644 --- a/database/settings/2023_02_01_182043_create_discord_settings.php +++ b/database/settings/2023_02_01_182043_create_discord_settings.php @@ -23,48 +23,52 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::DISCORD:BOT_TOKEN', - 'value' => $this->getNewValue('bot_token'), + 'value' => $this->getNewValue('bot_token', 'discord'), 'type' => 'string', 'description' => 'The bot token for the Discord bot.', ], [ 'key' => 'SETTINGS::DISCORD:CLIENT_ID', - 'value' => $this->getNewValue('client_id'), + 'value' => $this->getNewValue('client_id', 'discord'), 'type' => 'string', 'description' => 'The client ID for the Discord bot.', ], [ 'key' => 'SETTINGS::DISCORD:CLIENT_SECRET', - 'value' => $this->getNewValue('client_secret'), + 'value' => $this->getNewValue('client_secret', 'discord'), 'type' => 'string', 'description' => 'The client secret for the Discord bot.', ], [ 'key' => 'SETTINGS::DISCORD:GUILD_ID', - 'value' => $this->getNewValue('guild_id'), + 'value' => $this->getNewValue('guild_id', 'discord'), 'type' => 'string', 'description' => 'The guild ID for the Discord bot.', ], [ 'key' => 'SETTINGS::DISCORD:INVITE_URL', - 'value' => $this->getNewValue('invite_url'), + 'value' => $this->getNewValue('invite_url', 'discord'), 'type' => 'string', 'description' => 'The invite URL for the Discord bot.', ], [ 'key' => 'SETTINGS::DISCORD:ROLE_ID', - 'value' => $this->getNewValue('role_id'), + 'value' => $this->getNewValue('role_id', 'discord'), 'type' => 'string', 'description' => 'The role ID for the Discord bot.', ] ]); - $this->migrator->delete('discord.bot_token'); - $this->migrator->delete('discord.client_id'); - $this->migrator->delete('discord.client_secret'); - $this->migrator->delete('discord.guild_id'); - $this->migrator->delete('discord.invite_url'); - $this->migrator->delete('discord.role_id'); + try { + $this->migrator->delete('discord.bot_token'); + $this->migrator->delete('discord.client_id'); + $this->migrator->delete('discord.client_secret'); + $this->migrator->delete('discord.guild_id'); + $this->migrator->delete('discord.invite_url'); + $this->migrator->delete('discord.role_id'); + } catch (Exception $e) { + // Do nothing. + } } } diff --git a/database/settings/2023_02_01_182108_create_locale_settings.php b/database/settings/2023_02_01_182108_create_locale_settings.php index 13dbde9dd..86f3d6e42 100644 --- a/database/settings/2023_02_01_182108_create_locale_settings.php +++ b/database/settings/2023_02_01_182108_create_locale_settings.php @@ -22,40 +22,44 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::LOCALE:AVAILABLE', - 'value' => $this->getNewValue('available'), + 'value' => $this->getNewValue('available', 'locale'), 'type' => 'string', 'description' => 'The available locales.', ], [ 'key' => 'SETTINGS::LOCALE:CLIENTS_CAN_CHANGE', - 'value' => $this->getNewValue('clients_can_change'), + 'value' => $this->getNewValue('clients_can_change', 'locale'), 'type' => 'boolean', 'description' => 'If clients can change their locale.', ], [ 'key' => 'SETTINGS::LOCALE:DATATABLES', - 'value' => $this->getNewValue('datatables'), + 'value' => $this->getNewValue('datatables', 'locale'), 'type' => 'string', 'description' => 'The locale for datatables.', ], [ 'key' => 'SETTINGS::LOCALE:DEFAULT', - 'value' => $this->getNewValue('default'), + 'value' => $this->getNewValue('default', 'locale'), 'type' => 'string', 'description' => 'The default locale.', ], [ 'key' => 'SETTINGS::LOCALE:DYNAMIC', - 'value' => $this->getNewValue('dynamic'), + 'value' => $this->getNewValue('dynamic', 'locale'), 'type' => 'boolean', 'description' => 'If the locale should be dynamic.', ], ]); - $this->migrator->delete('locale.available'); - $this->migrator->delete('locale.clients_can_change'); - $this->migrator->delete('locale.datatables'); - $this->migrator->delete('locale.default'); - $this->migrator->delete('locale.dynamic'); + try { + $this->migrator->delete('locale.available'); + $this->migrator->delete('locale.clients_can_change'); + $this->migrator->delete('locale.datatables'); + $this->migrator->delete('locale.default'); + $this->migrator->delete('locale.dynamic'); + } catch (Exception $e) { + // Do nothing + } } } diff --git a/database/settings/2023_02_01_182135_create_referral_settings.php b/database/settings/2023_02_01_182135_create_referral_settings.php index 839f0e28b..ed7ff3fd4 100644 --- a/database/settings/2023_02_01_182135_create_referral_settings.php +++ b/database/settings/2023_02_01_182135_create_referral_settings.php @@ -22,47 +22,51 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::REFERRAL::ALLOWED', - 'value' => $this->getNewValue('allowed'), + 'value' => $this->getNewValue('allowed', 'referral'), 'type' => 'string', 'description' => 'The allowed referral types.', ], [ 'key' => 'SETTINGS::REFERRAL::ALWAYS_GIVE_COMMISSION', - 'value' => $this->getNewValue('always_give_commission'), + 'value' => $this->getNewValue('always_give_commission', 'referral'), 'type' => 'boolean', 'description' => 'Whether to always give commission to the referrer.', ], [ 'key' => 'SETTINGS::REFERRAL::ENABLED', - 'value' => $this->getNewValue('enabled'), + 'value' => $this->getNewValue('enabled', 'referral'), 'type' => 'boolean', 'description' => 'Whether to enable the referral system.', ], [ 'key' => 'SETTINGS::REFERRAL::REWARD', - 'value' => $this->getNewValue('reward'), + 'value' => $this->getNewValue('reward', 'referral'), 'type' => 'integer', 'description' => 'The reward for the referral.', ], [ 'key' => 'SETTINGS::REFERRAL:MODE', - 'value' => $this->getNewValue('mode'), + 'value' => $this->getNewValue('mode', 'referral'), 'type' => 'string', 'description' => 'The referral mode.', ], [ 'key' => 'SETTINGS::REFERRAL:PERCENTAGE', - 'value' => $this->getNewValue('percentage'), + 'value' => $this->getNewValue('percentage', 'referral'), 'type' => 'integer', 'description' => 'The referral percentage.', ], ]); - $this->migrator->delete('referral.allowed'); - $this->migrator->delete('referral.always_give_commission'); - $this->migrator->delete('referral.enabled'); - $this->migrator->delete('referral.reward'); - $this->migrator->delete('referral.mode'); - $this->migrator->delete('referral.percentage'); + try { + $this->migrator->delete('referral.allowed'); + $this->migrator->delete('referral.always_give_commission'); + $this->migrator->delete('referral.enabled'); + $this->migrator->delete('referral.reward'); + $this->migrator->delete('referral.mode'); + $this->migrator->delete('referral.percentage'); + } catch (Exception $e) { + // + } } } diff --git a/database/settings/2023_02_01_182158_create_website_settings.php b/database/settings/2023_02_01_182158_create_website_settings.php index 2bbdd5b57..eb8f3295d 100644 --- a/database/settings/2023_02_01_182158_create_website_settings.php +++ b/database/settings/2023_02_01_182158_create_website_settings.php @@ -33,68 +33,72 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::SYSTEM:MOTD_ENABLED', - 'value' => $this->getNewValue('motd_enabled'), + 'value' => $this->getNewValue('motd_enabled', 'website'), 'type' => 'boolean', 'description' => 'Enable or disable the MOTD.', ], [ 'key' => 'SETTINGS::SYSTEM:MOTD_MESSAGE', - 'value' => $this->getNewValue('motd_message'), + 'value' => $this->getNewValue('motd_message', 'website'), 'type' => 'text', 'description' => 'The message that will be displayed in the MOTD.', ], [ 'key' => 'SETTINGS::SYSTEM:SHOW_IMPRINT', - 'value' => $this->getNewValue('show_imprint'), + 'value' => $this->getNewValue('show_imprint', 'website'), 'type' => 'boolean', 'description' => 'Enable or disable the imprint.', ], [ 'key' => 'SETTINGS::SYSTEM:SHOW_PRIVACY', - 'value' => $this->getNewValue('show_privacy'), + 'value' => $this->getNewValue('show_privacy', 'website'), 'type' => 'boolean', 'description' => 'Enable or disable the privacy policy.', ], [ 'key' => 'SETTINGS::SYSTEM:SHOW_TOS', - 'value' => $this->getNewValue('show_tos'), + 'value' => $this->getNewValue('show_tos', 'website'), 'type' => 'boolean', 'description' => 'Enable or disable the terms of service.', ], [ 'key' => 'SETTINGS::SYSTEM:USEFULLINKS_ENABLED', - 'value' => $this->getNewValue('useful_links_enabled'), + 'value' => $this->getNewValue('useful_links_enabled', 'website'), 'type' => 'boolean', 'description' => 'Enable or disable the useful links.', ], [ 'key' => 'SETTINGS::SYSTEM:SEO_TITLE', - 'value' => $this->getNewValue('seo_title'), + 'value' => $this->getNewValue('seo_title', 'website'), 'type' => 'string', 'description' => 'The title of the website.', ], [ 'key' => 'SETTINGS::SYSTEM:SEO_DESCRIPTION', - 'value' => $this->getNewValue('seo_description'), + 'value' => $this->getNewValue('seo_description', 'website'), 'type' => 'string', 'description' => 'The description of the website.', ], [ 'key' => 'SETTINGS::SYSTEM:ENABLE_LOGIN_LOGO', - 'value' => $this->getNewValue('enable_login_logo'), + 'value' => $this->getNewValue('enable_login_logo', 'website'), 'type' => 'boolean', 'description' => 'Enable or disable the login logo.', ] ]); - $this->migrator->delete('website.motd_enabled'); - $this->migrator->delete('website.motd_message'); - $this->migrator->delete('website.show_imprint'); - $this->migrator->delete('website.show_privacy'); - $this->migrator->delete('website.show_tos'); - $this->migrator->delete('website.useful_links_enabled'); - $this->migrator->delete('website.seo_title'); - $this->migrator->delete('website.seo_description'); - $this->migrator->delete('website.enable_login_logo'); + try { + $this->migrator->delete('website.motd_enabled'); + $this->migrator->delete('website.motd_message'); + $this->migrator->delete('website.show_imprint'); + $this->migrator->delete('website.show_privacy'); + $this->migrator->delete('website.show_tos'); + $this->migrator->delete('website.useful_links_enabled'); + $this->migrator->delete('website.seo_title'); + $this->migrator->delete('website.seo_description'); + $this->migrator->delete('website.enable_login_logo'); + } catch (Exception $e) { + // Do nothing + } } } diff --git a/database/settings/2023_02_04_181156_create_ticket_settings.php b/database/settings/2023_02_04_181156_create_ticket_settings.php index 41eefe737..b4cfd8f2f 100644 --- a/database/settings/2023_02_04_181156_create_ticket_settings.php +++ b/database/settings/2023_02_04_181156_create_ticket_settings.php @@ -19,19 +19,23 @@ public function down(): void DB::table('settings_old')->insert([ [ 'key' => 'SETTINGS::TICKET:NOTIFY', - 'value' => $this->getNewValue('notify'), + 'value' => $this->getNewValue('notify', 'ticket'), 'type' => 'string', 'description' => 'The notification type for tickets.', ], [ 'key' => 'SETTINGS::TICKET:ENABLED', - 'value' => $this->getNewValue('enabled'), + 'value' => $this->getNewValue('enabled', 'ticket'), 'type' => 'boolean', 'description' => 'Enable or disable the ticket system.', ] ]); - $this->migrator->delete('ticket.enabled'); - $this->migrator->delete('ticket.notify'); + try { + $this->migrator->delete('ticket.enabled'); + $this->migrator->delete('ticket.notify'); + } catch (Exception $e) { + // Do nothing. + } } } diff --git a/database/settings/2023_05_07_195343_ticket_information.php b/database/settings/2023_05_07_195343_delete_notify_add_ticket_information.php similarity index 70% rename from database/settings/2023_05_07_195343_ticket_information.php rename to database/settings/2023_05_07_195343_delete_notify_add_ticket_information.php index 21d9aa67b..68ec45556 100644 --- a/database/settings/2023_05_07_195343_ticket_information.php +++ b/database/settings/2023_05_07_195343_delete_notify_add_ticket_information.php @@ -9,4 +9,10 @@ public function up(): void $this->migrator->delete('ticket.notify'); $this->migrator->add('ticket.information', "Can't start your server? Need an additional port? Do you have any other questions? Let us know by opening a ticket."); } + + public function down(): void + { + $this->migrator->add('ticket.notify', 'all'); + $this->migrator->delete('ticket.information'); + } }; From 931f08a1d7e73fdbfe5bfe2041e32d5ce0c2c6e1 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Wed, 24 Apr 2024 10:47:39 +0200 Subject: [PATCH 260/514] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Try=20Catch=20for?= =?UTF-8?q?=20upgrade=20routing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/web.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/routes/web.php b/routes/web.php index 42d2edf37..c575ea981 100644 --- a/routes/web.php +++ b/routes/web.php @@ -34,6 +34,7 @@ use App\Http\Controllers\TranslationController; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route; /* @@ -80,10 +81,10 @@ try { $serverSettings = app(App\Settings\ServerSettings::class); if ($serverSettings->creation_enabled) { - Route::resource('servers', ServerController::class); + Route::post('servers/{server}/upgrade', [ServerController::class, 'upgrade'])->name('servers.upgrade'); } } catch (Exception $e) { - // Do nothing if the settings are not available. + Log::error("ServerSettings not found, skipping server upgrade route"); } Route::post('profile/selfdestruct', [ProfileController::class, 'selfDestroyUser'])->name('profile.selfDestroyUser'); From e35bbddc40d65b581dccd584e916dffd775273b2 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Wed, 24 Apr 2024 10:54:06 +0200 Subject: [PATCH 261/514] =?UTF-8?q?chore:=20=E2=99=BB=EF=B8=8F=20Add=20enc?= =?UTF-8?q?rypted=20prop=20back=20in?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Settings/DiscordSettings.php | 2 -- app/Settings/MailSettings.php | 7 ++++++- app/Settings/PterodactylSettings.php | 8 +++++++- .../2023_02_01_181334_create_pterodactyl_settings.php | 2 -- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/Settings/DiscordSettings.php b/app/Settings/DiscordSettings.php index 4788ae7eb..79981c71b 100644 --- a/app/Settings/DiscordSettings.php +++ b/app/Settings/DiscordSettings.php @@ -18,8 +18,6 @@ public static function group(): string return 'discord'; } - - /** * Summary of validations array * @return array<string, string> diff --git a/app/Settings/MailSettings.php b/app/Settings/MailSettings.php index 90b5a3283..c3a9a367f 100644 --- a/app/Settings/MailSettings.php +++ b/app/Settings/MailSettings.php @@ -20,7 +20,12 @@ public static function group(): string return 'mail'; } - + public static function encrypted(): array + { + return [ + 'mail_password', + ]; + } public function setConfig() { diff --git a/app/Settings/PterodactylSettings.php b/app/Settings/PterodactylSettings.php index 3e888b937..c96933867 100644 --- a/app/Settings/PterodactylSettings.php +++ b/app/Settings/PterodactylSettings.php @@ -16,7 +16,13 @@ public static function group(): string return 'pterodactyl'; } - + public static function encrypted(): array + { + return [ + 'admin_token', + 'user_token', + ]; + } /** * Get url with ensured ending backslash diff --git a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php index 1bf0c7c08..dbbb93b67 100644 --- a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php +++ b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php @@ -11,8 +11,6 @@ public function up(): void $this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN', '') : env('PTERODACTYL_TOKEN', '')); $this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN', '') : ''); - // $this->migrator->add('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN') : env('PTERODACTYL_TOKEN', '')); - // $this->migrator->add('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN') : ''); $this->migrator->add('pterodactyl.panel_url', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:URL', '') : env('PTERODACTYL_URL', '')); $this->migrator->add('pterodactyl.per_page_limit', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT', 200) : 200); } From 4404d4a0aa75bb7f7d370ebe51da0eb752e6a51f Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:24:04 -0300 Subject: [PATCH 262/514] Standardization and Organization I standardized the functions to be the same as other payments, I removed MpPayment and only put it for the webhook to set the payment status (as done in Mollie) I changed the way Success works to the same as Mollie, I didn't understand what @ IceToast talked about Spatie, to me the way to call the Mercado Pago token appears to be correct --- .../MercadoPago/MercadoPagoExtension.php | 127 ++++++------------ .../MercadoPago/web_routes.php | 6 +- 2 files changed, 46 insertions(+), 87 deletions(-) diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php index c37d640e6..ffd545d18 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -48,9 +48,9 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct 'Authorization' => 'Bearer ' . $settings->access_token, ])->post($url, [ 'back_urls' => [ - 'success' => route('payment.MercadoPagoChecker'), + 'success' => route('payment.MercadoPagoSuccess'), 'failure' => route('payment.Cancel'), - 'pending' => route('payment.MercadoPagoChecker'), + 'pending' => route('payment.MercadoPagoSuccess'), ], 'notification_url' => route('payment.MercadoPagoWebhook'), 'payer' => [ @@ -73,8 +73,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct ]); if ($response->successful()) { - // Redirect link - Redirect::to($response->json()['init_point'])->send(); + return $response->json()['init_point']; } else { Log::error('MercadoPago Payment: ' . $response->body()); throw new Exception('Payment failed'); @@ -85,27 +84,13 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct } } - static function Checker(Request $request): void + static function Success(Request $request): void { - // paymentID (not is preferenceID or paymentID for store) - $paymentId = $request->input('payment_id'); - - $MpPayment = self::MpPayment($paymentId, false); - - switch ($MpPayment) { - case "paid": - Redirect::route('home')->with('success', 'Payment successful')->send(); - break; - case "cancelled": - Redirect::route('home')->with('info', 'Your canceled the payment')->send(); - break; - case "processing": - Redirect::route('home')->with('info', 'Your payment is being processed')->send(); - break; - default: - Redirect::route('home')->with('error', 'Your payment is unknown')->send(); - break; - } + $payment = Payment::findOrFail($request->input('payment')); + $payment->status = PaymentStatus::PROCESSING; + $payment->save(); + Redirect::route('home')->with('success', 'Your payment is being processed')->send(); + return; } static function Webhook(Request $request): JsonResponse @@ -126,7 +111,40 @@ static function Webhook(Request $request): JsonResponse // mercado pago api test return response()->json(['success' => true]); } else { - self::MpPayment($notificationId, true); + $url = "https://api.mercadopago.com/v1/payments/" . $notificationId; + $settings = new MercadoPagoSettings(); + $response = Http::withHeaders([ + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $settings->access_token, + ])->get($url); + + if ($response->successful()) { + $mercado = $response->json(); + $status = $mercado['status']; + $payment = Payment::findOrFail($mercado['metadata']['crtl_panel_payment_id']); + $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); + if ($status == "approved") { + // avoids double additions, if the user enters after the webhook has already added the credits + if ($payment->status !== PaymentStatus::PAID) { + $user = User::findOrFail($payment->user_id); + $payment->status = PaymentStatus::PAID; + $payment->save(); + $user->notify(new ConfirmPaymentNotification($payment)); + event(new PaymentEvent($user, $payment, $shopProduct)); + event(new UserUpdateCreditsEvent($user)); + } + } else { + if ($status == "cancelled") { + $user = User::findOrFail($payment->user_id); + $payment->status = PaymentStatus::CANCELED; + } else { + $user = User::findOrFail($payment->user_id); + $payment->status = PaymentStatus::PROCESSING; + } + $payment->save(); + event(new PaymentEvent($user, $payment, $shopProduct)); + } + } } } catch (Exception $ex) { Log::error('MercadoPago Webhook(IPN) Payment: ' . $ex->getMessage()); @@ -135,63 +153,4 @@ static function Webhook(Request $request): JsonResponse } return response()->json(['success' => true]); } - /** - * Mercado Pago Payment checker - */ - private function MpPayment(string $paymentID, bool $notification): string - { - $MpResponse = "unknown"; - $url = "https://api.mercadopago.com/v1/payments/" . $paymentID; - $settings = new MercadoPagoSettings(); - $response = Http::withHeaders([ - 'Content-Type' => 'application/json', - 'Authorization' => 'Bearer ' . $settings->access_token, - ])->get($url); - - if ($response->successful()) { - $mercado = $response->json(); - $status = $mercado['status']; - $payment = Payment::findOrFail($mercado['metadata']['crtl_panel_payment_id']); - $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); - - if ($status == "approved") { - // avoids double additions, if the user enters after the webhook has already added the credits - if ($payment->status !== PaymentStatus::PAID) { - $user = User::findOrFail($payment->user_id); - $payment->update([ - 'status' => PaymentStatus::PAID, - 'payment_id' => $paymentID, - ]); - $payment->save(); - if ($notification) { - $user->notify(new ConfirmPaymentNotification($payment)); - } - event(new PaymentEvent($user, $payment, $shopProduct)); - event(new UserUpdateCreditsEvent($user)); - } - $MpResponse = "paid"; - } else { - if ($status == "cancelled") { - $user = User::findOrFail($payment->user_id); - $payment->update([ - 'status' => PaymentStatus::CANCELED, - 'payment_id' => $paymentID, - ]); - $payment->save(); - event(new PaymentEvent($user, $payment, $shopProduct)); - $MpResponse = "cancelled"; - } else { - $user = User::findOrFail($payment->user_id); - $payment->update([ - 'status' => PaymentStatus::PROCESSING, - 'payment_id' => $paymentID, - ]); - $payment->save(); - event(new PaymentEvent($user, $payment, $shopProduct)); - $MpResponse = "processing"; - } - } - } - return $MpResponse; - } } diff --git a/app/Extensions/PaymentGateways/MercadoPago/web_routes.php b/app/Extensions/PaymentGateways/MercadoPago/web_routes.php index a43179a64..e826d6da5 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/web_routes.php +++ b/app/Extensions/PaymentGateways/MercadoPago/web_routes.php @@ -5,11 +5,11 @@ Route::middleware(['web', 'auth'])->group(function () { Route::get( - 'payment/MercadoPagoChecker', + 'payment/MercadoPagoSuccess', function () { - MercadoPagoExtension::Checker(request()); + MercadoPagoExtension::Success(request()); } - )->name('payment.MercadoPagoChecker'); + )->name('payment.MercadoPagoSuccess'); }); From 312e53a01e1e97e0f928a0eb30e43877cc703649 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 30 Apr 2024 14:07:42 +0200 Subject: [PATCH 263/514] UPDATE: moved the developement docker into a developement folder --- docker/{ => development}/docker-compose.yml | 12 ++++++------ docker/{ => development}/nginx/Dockerfile | 4 ++-- docker/{ => development}/nginx/default.conf | 0 docker/{ => development}/nginx/nginx.conf | 0 docker/{ => development}/php/Dockerfile | 2 +- docker/{ => development}/php/www.conf | 0 6 files changed, 9 insertions(+), 9 deletions(-) rename docker/{ => development}/docker-compose.yml (82%) rename docker/{ => development}/nginx/Dockerfile (60%) rename docker/{ => development}/nginx/default.conf (100%) rename docker/{ => development}/nginx/nginx.conf (100%) rename docker/{ => development}/php/Dockerfile (90%) rename docker/{ => development}/php/www.conf (100%) diff --git a/docker/docker-compose.yml b/docker/development/docker-compose.yml similarity index 82% rename from docker/docker-compose.yml rename to docker/development/docker-compose.yml index 56c9e24de..16c9bf94f 100644 --- a/docker/docker-compose.yml +++ b/docker/development/docker-compose.yml @@ -6,13 +6,13 @@ networks: services: nginx: build: - context: ../ - dockerfile: docker/nginx/Dockerfile + context: ../../ + dockerfile: docker/development/nginx/Dockerfile container_name: controlpanel_nginx ports: - 80:80 volumes: - - "../:/var/www/html" + - "../../:/var/www/html" depends_on: - php - mysql @@ -38,11 +38,11 @@ services: php: build: - context: ../ - dockerfile: docker/php/Dockerfile + context: ../../ + dockerfile: docker/development/php/Dockerfile container_name: controlpanel_php volumes: - - "../:/var/www/html" + - "../../:/var/www/html" networks: - laravel diff --git a/docker/nginx/Dockerfile b/docker/development/nginx/Dockerfile similarity index 60% rename from docker/nginx/Dockerfile rename to docker/development/nginx/Dockerfile index 51bf97ea1..deca865f1 100644 --- a/docker/nginx/Dockerfile +++ b/docker/development/nginx/Dockerfile @@ -2,8 +2,8 @@ FROM nginx:stable-alpine RUN addgroup -g 1000 laravel && adduser -G laravel -g laravel -s /bin/sh -D laravel -ADD ./docker/nginx/nginx.conf /etc/nginx/ -ADD ./docker/nginx/default.conf /etc/nginx/conf.d/ +ADD ./docker/development/nginx/nginx.conf /etc/nginx/ +ADD ./docker/development/nginx/default.conf /etc/nginx/conf.d/ RUN mkdir -p /var/www/html diff --git a/docker/nginx/default.conf b/docker/development/nginx/default.conf similarity index 100% rename from docker/nginx/default.conf rename to docker/development/nginx/default.conf diff --git a/docker/nginx/nginx.conf b/docker/development/nginx/nginx.conf similarity index 100% rename from docker/nginx/nginx.conf rename to docker/development/nginx/nginx.conf diff --git a/docker/php/Dockerfile b/docker/development/php/Dockerfile similarity index 90% rename from docker/php/Dockerfile rename to docker/development/php/Dockerfile index 4b8d91dd9..3ec761ae7 100644 --- a/docker/php/Dockerfile +++ b/docker/development/php/Dockerfile @@ -4,7 +4,7 @@ RUN apt-get update \ RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip gd bcmath -ADD ./docker/php/www.conf /usr/local/etc/php-fpm.d/ +ADD ./docker/development/php/www.conf /usr/local/etc/php-fpm.d/ RUN mkdir -p /var/www/html diff --git a/docker/php/www.conf b/docker/development/php/www.conf similarity index 100% rename from docker/php/www.conf rename to docker/development/php/www.conf From 31c79cbc99223ce73700e5786a7fd6cd49fc9a12 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 30 Apr 2024 14:07:53 +0200 Subject: [PATCH 264/514] ADD: added a standalone docker --- docker/standalone/Dockerfile | 45 +++ docker/standalone/docker-compose.yml | 18 ++ docker/standalone/nginx/default.conf | 21 ++ docker/standalone/nginx/nginx.conf | 29 ++ docker/standalone/php/www.conf | 439 +++++++++++++++++++++++++++ 5 files changed, 552 insertions(+) create mode 100644 docker/standalone/Dockerfile create mode 100644 docker/standalone/docker-compose.yml create mode 100644 docker/standalone/nginx/default.conf create mode 100644 docker/standalone/nginx/nginx.conf create mode 100644 docker/standalone/php/www.conf diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile new file mode 100644 index 000000000..a0efd6ed3 --- /dev/null +++ b/docker/standalone/Dockerfile @@ -0,0 +1,45 @@ +FROM php:8.1-fpm + +# Install Nginx and other dependencies +RUN apt-get update && apt-get install -y \ + nginx \ + curl \ + libcurl4-openssl-dev \ + libicu-dev \ + libzip-dev \ + && rm -rf /var/lib/apt/lists/* + +# Install PHP extensions +RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip + +# Copy the custom PHP-FPM configuration +ADD ./docker/standalone/php/www.conf /usr/local/etc/php-fpm.d/ + +# Create directory for application and set ownership +RUN mkdir -p /var/www/html && \ + groupadd -g 1000 laravel && \ + useradd -u 1000 -g laravel -ms /bin/bash laravel && \ + chown laravel:laravel /var/www/html + +# Set the working directory +WORKDIR /var/www/html + +# Copy Composer binary from Composer image +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# Configure Nginx +COPY ./docker/standalone/nginx/nginx.conf /etc/nginx/nginx.conf +COPY ./docker/standalone/nginx/default.conf /etc/nginx/conf.d/default.conf + +# Create directory for Nginx logs and set ownership +RUN mkdir -p /var/log/nginx && \ + chown -R laravel:laravel /var/log/nginx + +# Expose ports +EXPOSE 80 + +# Run composer install if composer.json is present +RUN if [ -f composer.json ]; then composer install --no-dev --optimize-autoloader; fi + +# Start both PHP-FPM and Nginx +CMD ["sh", "-c", "service nginx start && php-fpm -F"] diff --git a/docker/standalone/docker-compose.yml b/docker/standalone/docker-compose.yml new file mode 100644 index 000000000..a55c85d86 --- /dev/null +++ b/docker/standalone/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' + +services: + web: + build: + context: ../../ + dockerfile: ./docker/standalone/Dockerfile + container_name: controlpanel_web + ports: + - "80:80" + volumes: + - "../../:/var/www/html:delegated" + environment: + - DB_HOST=10.0.0.16 + - DB_PORT=3306 + - DB_DATABASE=controlpanel + - DB_USERNAME=controlpaneluser + - DB_PASSWORD=pass diff --git a/docker/standalone/nginx/default.conf b/docker/standalone/nginx/default.conf new file mode 100644 index 000000000..2fe700465 --- /dev/null +++ b/docker/standalone/nginx/default.conf @@ -0,0 +1,21 @@ +server { + listen 80; + server_name _; + root /var/www/html/public; + index index.php index.html index.htm index.nginx-debian.html; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass 127.0.0.1:9000; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/docker/standalone/nginx/nginx.conf b/docker/standalone/nginx/nginx.conf new file mode 100644 index 000000000..060e50253 --- /dev/null +++ b/docker/standalone/nginx/nginx.conf @@ -0,0 +1,29 @@ +user laravel; +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/docker/standalone/php/www.conf b/docker/standalone/php/www.conf new file mode 100644 index 000000000..eb028e111 --- /dev/null +++ b/docker/standalone/php/www.conf @@ -0,0 +1,439 @@ +; Start a new pool named 'www'. +; the variable $pool can be used in any directive and will be replaced by the +; pool name ('www' here) +[www] + +; Per pool prefix +; It only applies on the following directives: +; - 'access.log' +; - 'slowlog' +; - 'listen' (unixsocket) +; - 'chroot' +; - 'chdir' +; - 'php_values' +; - 'php_admin_values' +; When not set, the global prefix (or NONE) applies instead. +; Note: This directive can also be relative to the global prefix. +; Default Value: none +;prefix = /path/to/pools/$pool + +; Unix user/group of processes +; Note: The user is mandatory. If the group is not set, the default user's group +; will be used. +user = laravel +group = laravel + +; The address on which to accept FastCGI requests. +; Valid syntaxes are: +; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on +; a specific port; +; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on +; a specific port; +; 'port' - to listen on a TCP socket to all addresses +; (IPv6 and IPv4-mapped) on a specific port; +; '/path/to/unix/socket' - to listen on a unix socket. +; Note: This value is mandatory. +listen = 127.0.0.1:9000 + +; Set listen(2) backlog. +; Default Value: 511 (-1 on FreeBSD and OpenBSD) +;listen.backlog = 511 + +; Set permissions for unix socket, if one is used. In Linux, read/write +; permissions must be set in order to allow connections from a web server. Many +; BSD-derived systems allow connections regardless of permissions. The owner +; and group can be specified either by name or by their numeric IDs. +; Default Values: user and group are set as the running user +; mode is set to 0660 +;listen.owner = www-data +;listen.group = www-data +;listen.mode = 0660 +; When POSIX Access Control Lists are supported you can set them using +; these options, value is a comma separated list of user/group names. +; When set, listen.owner and listen.group are ignored +;listen.acl_users = +;listen.acl_groups = + +; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect. +; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original +; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address +; must be separated by a comma. If this value is left blank, connections will be +; accepted from any ip address. +; Default Value: any +;listen.allowed_clients = 127.0.0.1 + +; Specify the nice(2) priority to apply to the pool processes (only if set) +; The value can vary from -19 (highest priority) to 20 (lower priority) +; Note: - It will only work if the FPM master process is launched as root +; - The pool processes will inherit the master process priority +; unless it specified otherwise +; Default Value: no set +; process.priority = -19 + +; Set the process dumpable flag (PR_SET_DUMPABLE prctl) even if the process user +; or group is differrent than the master process user. It allows to create process +; core dump and ptrace the process for the pool user. +; Default Value: no +; process.dumpable = yes + +; Choose how the process manager will control the number of child processes. +; Possible Values: +; static - a fixed number (pm.max_children) of child processes; +; dynamic - the number of child processes are set dynamically based on the +; following directives. With this process management, there will be +; always at least 1 children. +; pm.max_children - the maximum number of children that can +; be alive at the same time. +; pm.start_servers - the number of children created on startup. +; pm.min_spare_servers - the minimum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is less than this +; number then some children will be created. +; pm.max_spare_servers - the maximum number of children in 'idle' +; state (waiting to process). If the number +; of 'idle' processes is greater than this +; number then some children will be killed. +; ondemand - no children are created at startup. Children will be forked when +; new requests will connect. The following parameter are used: +; pm.max_children - the maximum number of children that +; can be alive at the same time. +; pm.process_idle_timeout - The number of seconds after which +; an idle process will be killed. +; Note: This value is mandatory. +pm = dynamic + +; The number of child processes to be created when pm is set to 'static' and the +; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. +; This value sets the limit on the number of simultaneous requests that will be +; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. +; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP +; CGI. The below defaults are based on a server without much resources. Don't +; forget to tweak pm.* to fit your needs. +; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' +; Note: This value is mandatory. +pm.max_children = 5 + +; The number of child processes created on startup. +; Note: Used only when pm is set to 'dynamic' +; Default Value: (min_spare_servers + max_spare_servers) / 2 +pm.start_servers = 2 + +; The desired minimum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +pm.min_spare_servers = 1 + +; The desired maximum number of idle server processes. +; Note: Used only when pm is set to 'dynamic' +; Note: Mandatory when pm is set to 'dynamic' +pm.max_spare_servers = 3 + +; The number of seconds after which an idle process will be killed. +; Note: Used only when pm is set to 'ondemand' +; Default Value: 10s +;pm.process_idle_timeout = 10s; + +; The number of requests each child process should execute before respawning. +; This can be useful to work around memory leaks in 3rd party libraries. For +; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. +; Default Value: 0 +;pm.max_requests = 500 + +; The URI to view the FPM status page. If this value is not set, no URI will be +; recognized as a status page. It shows the following informations: +; pool - the name of the pool; +; process manager - static, dynamic or ondemand; +; start time - the date and time FPM has started; +; start since - number of seconds since FPM has started; +; accepted conn - the number of request accepted by the pool; +; listen queue - the number of request in the queue of pending +; connections (see backlog in listen(2)); +; max listen queue - the maximum number of requests in the queue +; of pending connections since FPM has started; +; listen queue len - the size of the socket queue of pending connections; +; idle processes - the number of idle processes; +; active processes - the number of active processes; +; total processes - the number of idle + active processes; +; max active processes - the maximum number of active processes since FPM +; has started; +; max children reached - number of times, the process limit has been reached, +; when pm tries to start more children (works only for +; pm 'dynamic' and 'ondemand'); +; Value are updated in real time. +; Example output: +; pool: www +; process manager: static +; start time: 01/Jul/2011:17:53:49 +0200 +; start since: 62636 +; accepted conn: 190460 +; listen queue: 0 +; max listen queue: 1 +; listen queue len: 42 +; idle processes: 4 +; active processes: 11 +; total processes: 15 +; max active processes: 12 +; max children reached: 0 +; +; By default the status page output is formatted as text/plain. Passing either +; 'html', 'xml' or 'json' in the query string will return the corresponding +; output syntax. Example: +; http://www.foo.bar/status +; http://www.foo.bar/status?json +; http://www.foo.bar/status?html +; http://www.foo.bar/status?xml +; +; By default the status page only outputs short status. Passing 'full' in the +; query string will also return status for each pool process. +; Example: +; http://www.foo.bar/status?full +; http://www.foo.bar/status?json&full +; http://www.foo.bar/status?html&full +; http://www.foo.bar/status?xml&full +; The Full status returns for each process: +; pid - the PID of the process; +; state - the state of the process (Idle, Running, ...); +; start time - the date and time the process has started; +; start since - the number of seconds since the process has started; +; requests - the number of requests the process has served; +; request duration - the duration in µs of the requests; +; request method - the request method (GET, POST, ...); +; request URI - the request URI with the query string; +; content length - the content length of the request (only with POST); +; user - the user (PHP_AUTH_USER) (or '-' if not set); +; script - the main script called (or '-' if not set); +; last request cpu - the %cpu the last request consumed +; it's always 0 if the process is not in Idle state +; because CPU calculation is done when the request +; processing has terminated; +; last request memory - the max amount of memory the last request consumed +; it's always 0 if the process is not in Idle state +; because memory calculation is done when the request +; processing has terminated; +; If the process is in Idle state, then informations are related to the +; last request the process has served. Otherwise informations are related to +; the current request being served. +; Example output: +; ************************ +; pid: 31330 +; state: Running +; start time: 01/Jul/2011:17:53:49 +0200 +; start since: 63087 +; requests: 12808 +; request duration: 1250261 +; request method: GET +; request URI: /test_mem.php?N=10000 +; content length: 0 +; user: - +; script: /home/fat/web/docs/php/test_mem.php +; last request cpu: 0.00 +; last request memory: 0 +; +; Note: There is a real-time FPM status monitoring sample web page available +; It's available in: /usr/local/share/php/fpm/status.html +; +; Note: The value must start with a leading slash (/). The value can be +; anything, but it may not be a good idea to use the .php extension or it +; may conflict with a real PHP file. +; Default Value: not set +;pm.status_path = /status + +; The ping URI to call the monitoring page of FPM. If this value is not set, no +; URI will be recognized as a ping page. This could be used to test from outside +; that FPM is alive and responding, or to +; - create a graph of FPM availability (rrd or such); +; - remove a server from a group if it is not responding (load balancing); +; - trigger alerts for the operating team (24/7). +; Note: The value must start with a leading slash (/). The value can be +; anything, but it may not be a good idea to use the .php extension or it +; may conflict with a real PHP file. +; Default Value: not set +;ping.path = /ping + +; This directive may be used to customize the response of a ping request. The +; response is formatted as text/plain with a 200 response code. +; Default Value: pong +;ping.response = pong + +; The access log file +; Default: not set +;access.log = log/$pool.access.log + +; The access log format. +; The following syntax is allowed +; %%: the '%' character +; %C: %CPU used by the request +; it can accept the following format: +; - %{user}C for user CPU only +; - %{system}C for system CPU only +; - %{total}C for user + system CPU (default) +; %d: time taken to serve the request +; it can accept the following format: +; - %{seconds}d (default) +; - %{miliseconds}d +; - %{mili}d +; - %{microseconds}d +; - %{micro}d +; %e: an environment variable (same as $_ENV or $_SERVER) +; it must be associated with embraces to specify the name of the env +; variable. Some exemples: +; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e +; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e +; %f: script filename +; %l: content-length of the request (for POST request only) +; %m: request method +; %M: peak of memory allocated by PHP +; it can accept the following format: +; - %{bytes}M (default) +; - %{kilobytes}M +; - %{kilo}M +; - %{megabytes}M +; - %{mega}M +; %n: pool name +; %o: output header +; it must be associated with embraces to specify the name of the header: +; - %{Content-Type}o +; - %{X-Powered-By}o +; - %{Transfert-Encoding}o +; - .... +; %p: PID of the child that serviced the request +; %P: PID of the parent of the child that serviced the request +; %q: the query string +; %Q: the '?' character if query string exists +; %r: the request URI (without the query string, see %q and %Q) +; %R: remote IP address +; %s: status (response code) +; %t: server time the request was received +; it can accept a strftime(3) format: +; %d/%b/%Y:%H:%M:%S %z (default) +; The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag +; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t +; %T: time the log has been written (the request has finished) +; it can accept a strftime(3) format: +; %d/%b/%Y:%H:%M:%S %z (default) +; The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag +; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t +; %u: remote user +; +; Default: "%R - %u %t \"%m %r\" %s" +;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" + +; The log file for slow requests +; Default Value: not set +; Note: slowlog is mandatory if request_slowlog_timeout is set +;slowlog = log/$pool.log.slow + +; The timeout for serving a single request after which a PHP backtrace will be +; dumped to the 'slowlog' file. A value of '0s' means 'off'. +; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) +; Default Value: 0 +;request_slowlog_timeout = 0 + +; Depth of slow log stack trace. +; Default Value: 20 +;request_slowlog_trace_depth = 20 + +; The timeout for serving a single request after which the worker process will +; be killed. This option should be used when the 'max_execution_time' ini option +; does not stop script execution for some reason. A value of '0' means 'off'. +; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) +; Default Value: 0 +;request_terminate_timeout = 0 + +; The timeout set by 'request_terminate_timeout' ini option is not engaged after +; application calls 'fastcgi_finish_request' or when application has finished and +; shutdown functions are being called (registered via register_shutdown_function). +; This option will enable timeout limit to be applied unconditionally +; even in such cases. +; Default Value: no +;request_terminate_timeout_track_finished = no + +; Set open file descriptor rlimit. +; Default Value: system defined value +;rlimit_files = 1024 + +; Set max core size rlimit. +; Possible Values: 'unlimited' or an integer greater or equal to 0 +; Default Value: system defined value +;rlimit_core = 0 + +; Chroot to this directory at the start. This value must be defined as an +; absolute path. When this value is not set, chroot is not used. +; Note: you can prefix with '$prefix' to chroot to the pool prefix or one +; of its subdirectories. If the pool prefix is not set, the global prefix +; will be used instead. +; Note: chrooting is a great security feature and should be used whenever +; possible. However, all PHP paths will be relative to the chroot +; (error_log, sessions.save_path, ...). +; Default Value: not set +;chroot = + +; Chdir to this directory at the start. +; Note: relative path can be used. +; Default Value: current directory or / when chroot +;chdir = /var/www + +; Redirect worker stdout and stderr into main error log. If not set, stdout and +; stderr will be redirected to /dev/null according to FastCGI specs. +; Note: on highloaded environement, this can cause some delay in the page +; process time (several ms). +; Default Value: no +;catch_workers_output = yes + +; Decorate worker output with prefix and suffix containing information about +; the child that writes to the log and if stdout or stderr is used as well as +; log level and time. This options is used only if catch_workers_output is yes. +; Settings to "no" will output data as written to the stdout or stderr. +; Default value: yes +;decorate_workers_output = no + +; Clear environment in FPM workers +; Prevents arbitrary environment variables from reaching FPM worker processes +; by clearing the environment in workers before env vars specified in this +; pool configuration are added. +; Setting to "no" will make all environment variables available to PHP code +; via getenv(), $_ENV and $_SERVER. +; Default Value: yes +;clear_env = no + +; Limits the extensions of the main script FPM will allow to parse. This can +; prevent configuration mistakes on the web server side. You should only limit +; FPM to .php extensions to prevent malicious users to use other extensions to +; execute php code. +; Note: set an empty value to allow all extensions. +; Default Value: .php +;security.limit_extensions = .php .php3 .php4 .php5 .php7 + +; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from +; the current environment. +; Default Value: clean env +;env[HOSTNAME] = $HOSTNAME +;env[PATH] = /usr/local/bin:/usr/bin:/bin +;env[TMP] = /tmp +;env[TMPDIR] = /tmp +;env[TEMP] = /tmp + +; Additional php.ini defines, specific to this pool of workers. These settings +; overwrite the values previously defined in the php.ini. The directives are the +; same as the PHP SAPI: +; php_value/php_flag - you can set classic ini defines which can +; be overwritten from PHP call 'ini_set'. +; php_admin_value/php_admin_flag - these directives won't be overwritten by +; PHP call 'ini_set' +; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no. + +; Defining 'extension' will load the corresponding shared extension from +; extension_dir. Defining 'disable_functions' or 'disable_classes' will not +; overwrite previously defined php.ini values, but will append the new value +; instead. + +; Note: path INI options can be relative and will be expanded with the prefix +; (pool, global or /usr/local) + +; Default Value: nothing is defined by default except the values in php.ini and +; specified at startup with the -d argument +;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com +;php_flag[display_errors] = off +;php_admin_value[error_log] = /var/log/fpm-php.www.log +;php_admin_flag[log_errors] = on +;php_admin_value[memory_limit] = 32M From d5ffe982d8c4f0ee21b4cddbea6abd0176531963 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 30 Apr 2024 14:28:48 +0200 Subject: [PATCH 265/514] UPDATE: moved building.md to a readme in the developement docker --- BUILDING.md => docker/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename BUILDING.md => docker/README.md (100%) diff --git a/BUILDING.md b/docker/README.md similarity index 100% rename from BUILDING.md rename to docker/README.md From 75c6dfcb798089bb188493bb395359f2d734eca0 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 30 Apr 2024 14:29:03 +0200 Subject: [PATCH 266/514] UPDATE: moved contributing.md to .github --- CONTRIBUTING.md => .github/CONTRIBUTING.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (100%) diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md From 5c32ff24912bfeac82a11d039a0b50a80482e2cc Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 30 Apr 2024 14:29:18 +0200 Subject: [PATCH 267/514] ADD: empty code of conduct in .github --- .github/CODE_OF_CONDUCT.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/CODE_OF_CONDUCT.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..e69de29bb From bc190dceb6f62229c5018fabc07c50207c2bda16 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 30 Apr 2024 14:41:18 +0200 Subject: [PATCH 268/514] FIX: bug and feature displaying differently --- .github/ISSUE_TEMPLATE/bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 24b38f4fb..f9e8e9ec3 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -1,6 +1,6 @@ name: "\U0001F41B Bug report" description: Create a report to help us improve -title: "[Bug]: " +title: "[Bug] " labels: ["bug"] body: - type: textarea From 971458a01b8d5a35c66730d051c244441f035c51 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 30 Apr 2024 14:46:10 +0200 Subject: [PATCH 269/514] ADD: empty issue template --- .github/PULL_REQUEST_TEMPLATE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..e69de29bb From d10c7ac4aaabbb6c70e945a28629aa423e2425b7 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 3 May 2024 13:25:58 +0200 Subject: [PATCH 270/514] REMOVED: binary for docker compose execution that where not used anymore --- bin/rebuild.sh | 2 -- bin/startdocker.sh | 2 -- bin/stopdocker.sh | 1 - 3 files changed, 5 deletions(-) delete mode 100644 bin/rebuild.sh delete mode 100644 bin/startdocker.sh delete mode 100644 bin/stopdocker.sh diff --git a/bin/rebuild.sh b/bin/rebuild.sh deleted file mode 100644 index 7aba0c5cc..000000000 --- a/bin/rebuild.sh +++ /dev/null @@ -1,2 +0,0 @@ -docker-compose -f docker/docker-compose.yml down -docker-compose -f docker/docker-compose.yml build --no-cache diff --git a/bin/startdocker.sh b/bin/startdocker.sh deleted file mode 100644 index 57a6c5154..000000000 --- a/bin/startdocker.sh +++ /dev/null @@ -1,2 +0,0 @@ -docker-compose -f docker/docker-compose.yml down -docker-compose -f docker/docker-compose.yml up -d --force-recreate --remove-orphans diff --git a/bin/stopdocker.sh b/bin/stopdocker.sh deleted file mode 100644 index f0bed643b..000000000 --- a/bin/stopdocker.sh +++ /dev/null @@ -1 +0,0 @@ -docker-compose -f docker/docker-compose.yml down From e3403963d3509f6f639da5323e24e8e5c2a04387 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 3 May 2024 13:28:03 +0200 Subject: [PATCH 271/514] UPDATE: moved the building.md to the developement docker --- BUILDING.md => docker/development/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename BUILDING.md => docker/development/README.md (100%) diff --git a/BUILDING.md b/docker/development/README.md similarity index 100% rename from BUILDING.md rename to docker/development/README.md From 96041d1eac02f85847156b50c04412adc50495c7 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 3 May 2024 13:28:15 +0200 Subject: [PATCH 272/514] ADD: empty readme for the standalone docker --- docker/standalone/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docker/standalone/README.md diff --git a/docker/standalone/README.md b/docker/standalone/README.md new file mode 100644 index 000000000..e69de29bb From b52aeb229616e689438fe50b4df1adab4347c7aa Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Sun, 5 May 2024 18:22:28 +0200 Subject: [PATCH 273/514] UPDATE: made a working standalone docker whit a startup script for easy deployment and managability --- docker/standalone/Dockerfile | 20 +++++++++++--------- docker/standalone/docker-compose.yml | 13 ++++--------- docker/standalone/scripts/startup.sh | 23 +++++++++++++++++++++++ 3 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 docker/standalone/scripts/startup.sh diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index a0efd6ed3..aafd194ad 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -16,20 +16,19 @@ RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip ADD ./docker/standalone/php/www.conf /usr/local/etc/php-fpm.d/ # Create directory for application and set ownership -RUN mkdir -p /var/www/html && \ +RUN mkdir -p /var/default && \ groupadd -g 1000 laravel && \ useradd -u 1000 -g laravel -ms /bin/bash laravel && \ - chown laravel:laravel /var/www/html + chown laravel:laravel /var/default -# Set the working directory -WORKDIR /var/www/html +# Copy application files into the container +COPY . /var/default # Copy Composer binary from Composer image COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Configure Nginx COPY ./docker/standalone/nginx/nginx.conf /etc/nginx/nginx.conf -COPY ./docker/standalone/nginx/default.conf /etc/nginx/conf.d/default.conf # Create directory for Nginx logs and set ownership RUN mkdir -p /var/log/nginx && \ @@ -38,8 +37,11 @@ RUN mkdir -p /var/log/nginx && \ # Expose ports EXPOSE 80 -# Run composer install if composer.json is present -RUN if [ -f composer.json ]; then composer install --no-dev --optimize-autoloader; fi +# Copy startup script +COPY ./docker/standalone/scripts/startup.sh /usr/local/bin/startup-script.sh -# Start both PHP-FPM and Nginx -CMD ["sh", "-c", "service nginx start && php-fpm -F"] +# Make startup script executable +RUN chmod +x /usr/local/bin/startup-script.sh + +# Start the startup script +CMD ["/usr/local/bin/startup-script.sh"] diff --git a/docker/standalone/docker-compose.yml b/docker/standalone/docker-compose.yml index a55c85d86..ed5dc8478 100644 --- a/docker/standalone/docker-compose.yml +++ b/docker/standalone/docker-compose.yml @@ -1,18 +1,13 @@ version: '3' services: - web: + controlpanel_web: build: context: ../../ dockerfile: ./docker/standalone/Dockerfile - container_name: controlpanel_web + container_name: controlpanel ports: - "80:80" volumes: - - "../../:/var/www/html:delegated" - environment: - - DB_HOST=10.0.0.16 - - DB_PORT=3306 - - DB_DATABASE=controlpanel - - DB_USERNAME=controlpaneluser - - DB_PASSWORD=pass + - '/mnt/user/appdata/ctrlpanel/www/:/var/www/html:rw' + - '/mnt/user/appdata/ctrlpanel/nginx/:/etc/nginx/conf.d/:rw' diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh new file mode 100644 index 000000000..942e9f8e1 --- /dev/null +++ b/docker/standalone/scripts/startup.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Check if /var/www/html is empty or .env file doesn't exist +if [ -z "$(ls -A /var/www/html)" ] || [ ! -f "/var/www/html/.env" ]; then + # Copy everything from /var/default to /var/www/html + cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files + + # Copy default Nginx configuration + cp -n /var/default/docker/standalone/nginx/default.conf /etc/nginx/conf.d/default.conf + + # Execute composer install if composer.json is present and there's no vendor directory + if [ -f "/var/www/html/composer.json" ] && [ ! -d "/var/www/html/vendor" ]; then + cd /var/www/html + composer install --no-dev --optimize-autoloader + cd - + fi +fi + +# Start Nginx +service nginx start + +# Start PHP-FPM +php-fpm -F From 37e505c37eb286fc9d62b32839ed6f360dea7b4c Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 6 May 2024 11:52:52 +0200 Subject: [PATCH 274/514] REMOVE: empty composer file at root --- composer | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 composer diff --git a/composer b/composer deleted file mode 100644 index e69de29bb..000000000 From 077ee2c7e4b325257dd3d160f161f931941c5347 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 6 May 2024 11:54:03 +0200 Subject: [PATCH 275/514] REMOVE: strange unused test.json file --- test.json | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 test.json diff --git a/test.json b/test.json deleted file mode 100644 index e0ff42154..000000000 --- a/test.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "PASSWORD": "", - "USERNAME": "", - "AUTO_UPDATE": "0", - "BOT_PY_FILE": "bot.py", - "PY_PACKAGES": "", - "USER_UPLOAD": "0", - "INSTALL_REPO": "", - "INSTALL_BRANCH": "" - } -] From 3656c8169d74300c8a3be2a52f3ee5067738066d Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 6 May 2024 11:59:44 +0200 Subject: [PATCH 276/514] UPDATE: cleared and documented git files --- .gitattributes | 5 +++++ .gitignore | 35 ++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/.gitattributes b/.gitattributes index 967315dd3..2f74b7dd8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,10 @@ +# Automatically detect text files * text=auto + +# Vendored files for specific languages *.css linguist-vendored *.scss linguist-vendored *.js linguist-vendored + +# Ignore CHANGELOG.md when exporting CHANGELOG.md export-ignore diff --git a/.gitignore b/.gitignore index ad9d095b8..f72249a83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,34 @@ +# Ignore dependencies and cache /node_modules +/vendor +/storage/*.key + +# Ignore public assets /public/hot /public/storage -/storage/*.key -/vendor -/storage/credit_deduction_log -storage/debugbar +/storage/app/public/logo.png + +# Ignore environment files and configuration .env .env.testing .env.backup -.idea +.env.dev + +# Ignore testing and debug logs .phpunit.result.cache -.editorconfig -docker-compose.override.yml -Homestead.json -Homestead.yaml npm-debug.log yarn-error.log yarn.lock + +# Ignore Docker and Homestead configuration +docker-compose.override.yml +Homestead.json +Homestead.yaml + +# Ignore gitignore itself .gitignore -.env.dev -.env.testing -storage/invoices.zip -storage/app/public/logo.png -*vscode - - Kopie.env + +# Ignore installation logs and locks public/install/logs.txt install.lock public/install/logs/installer.log From f18e8c817774edfde0475439065ab0f60aa2c241 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 6 May 2024 11:59:57 +0200 Subject: [PATCH 277/514] UPDATE: cleared and documented .env.exemple --- .env.example | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/.env.example b/.env.example index 98f9662bd..46f1a273a 100644 --- a/.env.example +++ b/.env.example @@ -4,25 +4,24 @@ APP_ENV=production APP_KEY= APP_DEBUG=false APP_URL=http://localhost -# List with timezones https://www.php.net/manual/en/timezones.php -APP_TIMEZONE=UTC +APP_TIMEZONE=UTC # List with timezones https://www.php.net/manual/en/timezones.php ### --- App Settings End --- ### -### --- DB Settings (required) --- ### +### --- Database Settings (required) --- ### DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=dashboard DB_USERNAME=dashboarduser DB_PASSWORD= -### --- DB Settings End --- ### +### --- Database Settings End --- ### - -# Google Recaptcha API Credentials - https://www.google.com/recaptcha/admin - reCaptcha V2 (not v3) +### --- Google Recaptcha Settings --- ### RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI RECAPTCHA_SECRET_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe +### --- Google Recaptcha Settings End --- ### -# Mail Server Settings - (HOST -> SMTP Server) +### --- Mail Server Settings --- ### MAIL_MAILER=smtp MAIL_HOST=mailhog MAIL_PORT=1025 @@ -31,25 +30,22 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" +### --- Mail Server Settings End --- ### - -# Laravel Logging Settings - https://laravel.com/docs/5.7/logging - Not needed to be changed +### --- Logging Settings --- ### LOG_CHANNEL=stack LOG_LEVEL=debug +### --- Logging Settings End --- ### -# Do not change anything below this line -BROADCAST_DRIVER=log +### --- Cache and Queue Settings --- ### CACHE_DRIVER=file QUEUE_CONNECTION=database SESSION_DRIVER=file SESSION_LIFETIME=120 +SETTINGS_CACHE_ENABLED=true +### --- Cache and Queue Settings End --- ### -MEMCACHED_HOST=127.0.0.1 - -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 - +### --- External Services Credentials --- ### AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 @@ -59,9 +55,15 @@ PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 +### --- External Services Credentials End --- ### + +### --- Additional Configuration --- ### +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" - -# Settings Cache -SETTINGS_CACHE_ENABLED=true +### --- Additional Configuration End --- ### From b2ee49f534059cc75115f2702970413f4adf94f8 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 6 May 2024 12:02:18 +0200 Subject: [PATCH 278/514] UPDATE: de-mixed the code of conduct from the contribute file --- .github/CODE_OF_CONDUCT.md | 25 +++++++++++++++++++++++++ .github/CONTRIBUTING.md | 26 -------------------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index e69de29bb..1714350ae 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,25 @@ +## Code of Conduct + +### Our Pledge +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +### Coding Style + +We are following the PSR12 code standard for PHP. + +### Our Standards +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 74ddb1f9f..c7d336441 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -15,29 +15,3 @@ php artisan translatable:export en 1. Give your PR a good descriptive title, so we can view immediately what the PR is about. 2. The dev team will look at your code and approve / merge when possible. 3. Make sure your PR follows our code of conduct and coding style. - -## Code of Conduct - -### Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -### Coding Style - -We are following the PSR12 code standard for PHP. - -### Our Standards -Examples of behavior that contributes to creating a positive environment include: - -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery and unwelcome sexual attention or advances -- Trolling, insulting/derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or electronic address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a professional setting From 0f138cab1dffd0c046b6854d2ae8133210e2f2f3 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 6 May 2024 12:13:35 +0200 Subject: [PATCH 279/514] ADD: basic Pull request tempalte --- .github/PULL_REQUEST_TEMPLATE.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e69de29bb..50d3e3885 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +⚠️ Please be sure that you have read pull request rules. + +Tick the checkbox if you understand [x]: +- [ ] I have read and understand the pull request rules. + +# Description + +Fixes #(issue) + +## Type of change + +Please delete any options that are not relevant. + +- Bug fix (non-breaking change which fixes an issue) +- User interface (UI) +- New feature (non-breaking change which adds functionality) +- Breaking change (a fix or feature that would cause existing functionality to not work as expected) +- Other +- This change requires a documentation update + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code and tested it +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generates no new warnings +- [ ] My code needed automated testing. I have added them (this is optional task) + +## Screenshots (if any) + +Please do not use any external image service. Instead, just paste in or drag and drop the image here, and it will be uploaded automatically. \ No newline at end of file From 9b45d76b445cf166c16c1ab026d967a8cabb2ee5 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 6 May 2024 12:30:52 +0200 Subject: [PATCH 280/514] ADD: Basic Security Policy --- .github/SECURITY.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/SECURITY.md diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 000000000..e3c6ecee8 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,17 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to ... + +Do not use the public issue tracker or discuss it in public as it will cause more damage. + +## Do you accept other 3rd-party bug bounty platforms? + +At this moment, we do not accept other bug bounty platforms, please report through GitHub Advisories only. We will ignore all 3rd-party bug bounty platforms emails. + +## Supported Versions + +### ControlPanel Versions + +You should use or upgrade to the latest version of ControlPanel. From 44a6946c44257bb5dc781c024c1d75003c3c0f6f Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 09:59:15 +0200 Subject: [PATCH 281/514] UPDATE: made a better security policy --- .github/SECURITY.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/SECURITY.md b/.github/SECURITY.md index e3c6ecee8..278e81e71 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -2,16 +2,16 @@ ## Reporting a Vulnerability -Please report security issues to ... +🛡️ If you discover a security vulnerability, please report it to us via GitHub Advisories. -Do not use the public issue tracker or discuss it in public as it will cause more damage. +⚠️ Please refrain from using the public issue tracker or discussing the vulnerability in public channels, as it may exacerbate the issue. -## Do you accept other 3rd-party bug bounty platforms? +## Acceptance of Bug Bounty Platforms -At this moment, we do not accept other bug bounty platforms, please report through GitHub Advisories only. We will ignore all 3rd-party bug bounty platforms emails. +At this time, we only accept vulnerability reports through GitHub Advisories. We kindly ask that you do not submit reports via other third-party bug bounty platforms, as they will be disregarded. ## Supported Versions ### ControlPanel Versions -You should use or upgrade to the latest version of ControlPanel. +We strongly recommend using or upgrading to the latest version of ControlPanel to ensure you have access to the latest security fixes and enhancements. From f12ab58c8e0d3cb91e4a44b403a71f0db68fbec8 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 09:59:27 +0200 Subject: [PATCH 282/514] UPDATE: made a better code of conduct --- .github/CODE_OF_CONDUCT.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 1714350ae..6b807f423 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,13 +1,10 @@ ## Code of Conduct -### Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +### 🤝 Our Pledge -### Coding Style +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. -We are following the PSR12 code standard for PHP. - -### Our Standards +### 🌟 Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language From cad6361e492e312dcd011ef6ff59c477a6b84618 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 09:59:39 +0200 Subject: [PATCH 283/514] UPDATE: made a better contributing policy --- .github/CONTRIBUTING.md | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c7d336441..ff44e2115 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,17 +1,34 @@ -# Contributing +# Contributing Guidelines -When contributing to this repository, please go through the open issues to see if you can contribute to something. If you want to contribute something that is not in the issues you can make an issue and wait for response from the dev team. +Thank you for considering contributing to this repository! Before making a contribution, please take a moment to review the following guidelines. -Please note we have a code of conduct, please follow it in all your interactions with the project. +## 🕵️♂️ Finding Tasks -If you added any Strings which are displayed at the frontend please localize them (e.g. "New String" -> {{ __('New String') }}) and run the localization string generation: +Check the open issues to see if there's something you can contribute to. If you have an idea or encounter a bug that's not already listed, feel free to create a new issue and wait for feedback from the development team. +## 🤝 Code of Conduct + +Please adhere to our [Code of Conduct](link-to-code-of-conduct) in all your interactions with the project. + +## 🌍 Localization + +If you add any strings that are displayed on the frontend, please localize them using the following format: +``` +"New String" -> {{ __('New String') }} +``` +After adding localized strings, run the following command to generate localization files: ```cmd php artisan translatable:export en ``` -## Pull request process +## 🚀 Pull Request Process + +1. Give your pull request (PR) a clear and descriptive title that summarizes the changes. +2. The development team will review your code and provide feedback or approve/merge it when appropriate. +3. Ensure that your PR follows our Code of Conduct and coding style guidelines. + +### 💻 Coding Style + +We follow the PSR12 code standard for PHP. -1. Give your PR a good descriptive title, so we can view immediately what the PR is about. -2. The dev team will look at your code and approve / merge when possible. -3. Make sure your PR follows our code of conduct and coding style. +Thank you for your contributions! 🎉 From 14e200f88e655a6f337677d7baf03854b90a7123 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 09:59:50 +0200 Subject: [PATCH 284/514] UPDATE: made a better pull request template --- .github/PULL_REQUEST_TEMPLATE.md | 38 ++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 50d3e3885..095ada5e9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,31 +1,35 @@ -⚠️ Please be sure that you have read pull request rules. +✨ Thank you for your contribution to our project! Before you submit your pull request, please take a moment to review and complete the following -Tick the checkbox if you understand [x]: -- [ ] I have read and understand the pull request rules. +⚠️ Please modify this template below and if not already one, read our pull request rules, Thanks! -# Description +Ensure that your pull request meets the following criteria: -Fixes #(issue) +- The code follows the style guidelines of this project +- You have performed a self-review of your own code and tested it +- You have commented your code, particularly in hard-to-understand areas +- Your changes generate no new warnings -## Type of change +--- -Please delete any options that are not relevant. +💡 **Description** + +Briefly describe the purpose of your pull request, including any relevant issue numbers it addresses. + +--- + +🛠️ **Type of Change** + +Please select the appropriate type of change: - Bug fix (non-breaking change which fixes an issue) -- User interface (UI) +- User interface (UI) improvement - New feature (non-breaking change which adds functionality) - Breaking change (a fix or feature that would cause existing functionality to not work as expected) - Other - This change requires a documentation update -## Checklist - -- [ ] My code follows the style guidelines of this project -- [ ] I have performed a self-review of my own code and tested it -- [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] My changes generates no new warnings -- [ ] My code needed automated testing. I have added them (this is optional task) +--- -## Screenshots (if any) +🖼️ **Screenshots (if applicable)** -Please do not use any external image service. Instead, just paste in or drag and drop the image here, and it will be uploaded automatically. \ No newline at end of file +If your pull request includes any visual changes, please provide screenshots here, do not use any external link. From 5ea4ceda6babd1468791b41c470ca53e71d36b54 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 10:00:00 +0200 Subject: [PATCH 285/514] UPDATE: made a better main readme --- README.md | 100 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index c121ff78a..092ac2171 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,81 @@ -### Features - -- PayPal, Stripe and Mollie Integration -- Hourly, Weekely, Monthly, Quarterly and Annual billing Cycles -- Referral System -- Partner System -- Ticket System -- Upgrade/Downgrade Server Resources -- Store (credit system with hourly billing and invoices) -- Email Verification -- Audit Log -- Admin Dashboard -- User/Server Management -- Customisable server plans -- Vouchers -- Alert System -- Theme Support -- and so much more! +<div align="center"> + <img src="./public/favicon" width="128" alt="" /> +</div> # CtrlPanel-gg - +CtrlPanel offers an easy-to-use and free billing solution for all starting and experienced hosting providers that seamlessly integrates with the Pterodactyl panel. It facilitates account creation, server ordering, and management, while offering addons, multiple payment methods, and customizable themes for a comprehensive solution. + + + +[](https://crowdin.com/project/controlpanelgg) + + - - -   [](https://crowdin.com/project/controlpanelgg)    -## About + -CtrlPanel's Dashboard is a dashboard application designed to offer clients a management tool to manage their pterodactyl servers. This dashboard comes with a credit-based billing solution that charges users depending on the billing cycle you chose for each server they have and suspends them if they run out of credits. +## ⭐ Features -This dashboard offers an easy to use and free billing solution for all starting and experienced hosting providers. This dashboard has many customisation options and added discord Oauth verification to offer a solid link between your discord server and your dashboard. You can check our [Demo here](https://demo.CtrlPanel.gg "Demo"). +- Store (credit system with hourly billing and invoices) +- Many Popular Payment Methods +- Referral +- Partner +- Vouchers +- Ticket +- Account Management +- Admin Dashboard and Tools +- Addon Support +- and more! -### [Installation](https://ctrlpanel.gg/docs/intro "Installation") +## ⛰️ Live Demo -### [Updating](https://ctrlpanel.gg/docs/Installation/updating "Updating") +Try it! -### [Discord](https://discord.gg/4Y6HjD2uyU "Discord") +Demo Server: [demo.CtrlPanel.gg](https://demo.CtrlPanel.gg) -### [Contributing](https://ctrlpanel.gg/docs/Contributing/contributing "Contributing") +It is a temporary live demo; all data will be deleted. -### [Donating](https://ctrlpanel.gg/docs/Contributing/donating "Donating") +## 🔧 How to Install +### 🐳 Docker +```bash +docker run ... +``` -# Preview +CtrlPanel is now running on [0.0.0.0:3001](http://0.0.0.0:3001). Don't forget to configure the database and Pterodactyl. [Documentation](documentation link here) -### Server Creation - +more info: [Docker](docker documentation link here) -### Overview - +### 💪🏻 Non-Docker -### Example server products - +Requirements: -### Ticket System - +- Platform + - Major Linux distros such as Debian, Ubuntu, CentOS, Fedora, and ArchLinux etc. + - Windows 10 (x64), Windows Server ... -### Voucher System - +Follow the [documentation](documentation link here) to know how to install. -### Partner System - +### MarketPlace +If you need more functionality, check out [Marketplace](https://market.ctrlpanel.gg/resources/). +## 🆙 How to Update +Please read: [Update Instructions](https://ctrlpanel.gg/docs/Installation/updating) + +## 🆕 What's Next? + +Roadmap: [CtrlPanel Roadmap](https://github.com/orgs/Ctrlpanel-gg/projects/1) + +## 🗣️ Discussion / Ask for Help + +For any general or technical questions, join CtrlPanel Discord for finding answers to your question. If you cannot find the information you need, feel free to ask. + +## 🤝 Contributing + +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. + +Thanks to all contributors and supporters! From 8c98acb20a5a0d9bc93ba5c1d7a228b9e8311191 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 10:18:22 +0200 Subject: [PATCH 286/514] FIX: links in main readme --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 092ac2171..36c11d002 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ <div align="center"> - <img src="./public/favicon" width="128" alt="" /> + <img src="https://ctrlpanel.gg/img/controlpanel.png" width="128" alt="" /> </div> # CtrlPanel-gg @@ -34,19 +34,21 @@ Try it! Demo Server: [demo.CtrlPanel.gg](https://demo.CtrlPanel.gg) -It is a temporary live demo; all data will be deleted. +<!-- It is a temporary live demo; all data will be deleted. --> ## 🔧 How to Install ### 🐳 Docker -```bash +Soon... + +<!-- ```bash docker run ... ``` CtrlPanel is now running on [0.0.0.0:3001](http://0.0.0.0:3001). Don't forget to configure the database and Pterodactyl. [Documentation](documentation link here) -more info: [Docker](docker documentation link here) +more info: [Docker](docker documentation link here) --> ### 💪🏻 Non-Docker @@ -56,7 +58,7 @@ Requirements: - Major Linux distros such as Debian, Ubuntu, CentOS, Fedora, and ArchLinux etc. - Windows 10 (x64), Windows Server ... -Follow the [documentation](documentation link here) to know how to install. +Follow the [documentation](https://ctrlpanel.gg/docs/intro) to know how to install. ### MarketPlace @@ -76,6 +78,6 @@ For any general or technical questions, join CtrlPanel Discord for finding answe ## 🤝 Contributing -Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. +Please read [CONTRIBUTING.md](https://github.com/Ctrlpanel-gg/panel/blob/main/.github/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. Thanks to all contributors and supporters! From f563dcdd741ed080d12d1c5fddd482143a5c5796 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 10:19:27 +0200 Subject: [PATCH 287/514] FIX: links in contributing.md --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ff44e2115..72eb32a7b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -8,7 +8,7 @@ Check the open issues to see if there's something you can contribute to. If you ## 🤝 Code of Conduct -Please adhere to our [Code of Conduct](link-to-code-of-conduct) in all your interactions with the project. +Please adhere to our [Code of Conduct](https://github.com/Ctrlpanel-gg/panel/blob/main/.github/CODE_OF_CONDUCT.md) in all your interactions with the project. ## 🌍 Localization From 6f55e306c1dce4756a0c21d23114abcee3cbf5f1 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 10:22:32 +0200 Subject: [PATCH 288/514] ADD: warning to default pull request text --- .github/PULL_REQUEST_TEMPLATE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 095ada5e9..89224a428 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ ✨ Thank you for your contribution to our project! Before you submit your pull request, please take a moment to review and complete the following -⚠️ Please modify this template below and if not already one, read our pull request rules, Thanks! +⚠️ Please modify this template below and if not already done, read our pull request rules, Thanks! Ensure that your pull request meets the following criteria: @@ -9,6 +9,8 @@ Ensure that your pull request meets the following criteria: - You have commented your code, particularly in hard-to-understand areas - Your changes generate no new warnings +Delete the above text and the following sections before submitting your pull request. + --- 💡 **Description** From 812b817968e656588f56e3a076082a03ee267051 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 10:32:24 +0200 Subject: [PATCH 289/514] ADD: donation to the project main readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 36c11d002..291c86cff 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,7 @@ For any general or technical questions, join CtrlPanel Discord for finding answe Please read [CONTRIBUTING.md](https://github.com/Ctrlpanel-gg/panel/blob/main/.github/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. Thanks to all contributors and supporters! + +## ♥️ Donations + +If you like what we do, please consider [supporting](https://ctrlpanel.gg/docs/Contributing/donating) us. From 1403985b24201d795a9199f3e0f7da26d73b3c85 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 13:52:51 +0200 Subject: [PATCH 290/514] UPDATE: added more php dependency to the standalone dockerfile --- docker/standalone/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index aafd194ad..62957baf0 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -7,10 +7,11 @@ RUN apt-get update && apt-get install -y \ libcurl4-openssl-dev \ libicu-dev \ libzip-dev \ + libpng-dev \ && rm -rf /var/lib/apt/lists/* # Install PHP extensions -RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip +RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip gd bcmath # Copy the custom PHP-FPM configuration ADD ./docker/standalone/php/www.conf /usr/local/etc/php-fpm.d/ From 469a814a6bb3898849ff55da2557e072237676cb Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 13:53:19 +0200 Subject: [PATCH 291/514] UPDATE: made a better standalone docker compose --- docker/standalone/docker-compose.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docker/standalone/docker-compose.yml b/docker/standalone/docker-compose.yml index ed5dc8478..cd980a2af 100644 --- a/docker/standalone/docker-compose.yml +++ b/docker/standalone/docker-compose.yml @@ -1,13 +1,15 @@ version: '3' services: - controlpanel_web: + controlpanel_standalone: build: context: ../../ dockerfile: ./docker/standalone/Dockerfile - container_name: controlpanel + container_name: controlpanel_standalone + restart: on-failure ports: - "80:80" + - "443:443" volumes: - - '/mnt/user/appdata/ctrlpanel/www/:/var/www/html:rw' - - '/mnt/user/appdata/ctrlpanel/nginx/:/etc/nginx/conf.d/:rw' + - './website_files:/var/www/html:rw' # change it + - './nginx_config:/etc/nginx/conf.d/:rw' # change it From cb2ea111e720badbe13626bf3972b8683716674a Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 13:53:37 +0200 Subject: [PATCH 292/514] UPDATE: made a better readme documentation for the standalone docker --- docker/standalone/README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docker/standalone/README.md b/docker/standalone/README.md index e69de29bb..0b7fa291b 100644 --- a/docker/standalone/README.md +++ b/docker/standalone/README.md @@ -0,0 +1,23 @@ +## 🐳 Standalone Docker + +The CtrlPanel standalone Docker enables users to run CtrlPanel easily with just a few clicks. + +To run CtrlPanel standalone Docker, you need to have Docker installed on your machine. Some server operating systems like Unraid, TrueNAS, etc.. already have Docker installed, making it even easier to run CtrlPanel. + +If you're using a different operating system, you can follow the official Docker installation guide [here](https://docs.docker.com/get-docker/). + +Once you have Docker installed, you can run CtrlPanel standalone Docker by executing the following command: + +```bash +docker run ... +``` + +This command will pull the latest CtrlPanel Docker image from Docker Hub and run it. + +The control panel will be available at http://localhost/install and will be a completely fresh installation. + +Note that while the container contains the full CtrlPanel installation, you will still need to perform the basic setup. You can find instructions for this [here](https://ctrlpanel.gg/docs/Installation/getting-started#basic-setup). + +## 🏗️ Advanced Docker + +If you are migrating, or want to create your own Docker image, you can follow the instructions [here](). From 5faea04fba77626e961d6b11a78ec0f7a638d367 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 14:21:10 +0200 Subject: [PATCH 293/514] UPDATE: updated the standalone docker configuration --- docker/standalone/README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docker/standalone/README.md b/docker/standalone/README.md index 0b7fa291b..992b80c82 100644 --- a/docker/standalone/README.md +++ b/docker/standalone/README.md @@ -1,23 +1,26 @@ -## 🐳 Standalone Docker +# 🐳 Standalone Docker The CtrlPanel standalone Docker enables users to run CtrlPanel easily with just a few clicks. To run CtrlPanel standalone Docker, you need to have Docker installed on your machine. Some server operating systems like Unraid, TrueNAS, etc.. already have Docker installed, making it even easier to run CtrlPanel. - If you're using a different operating system, you can follow the official Docker installation guide [here](https://docs.docker.com/get-docker/). Once you have Docker installed, you can run CtrlPanel standalone Docker by executing the following command: ```bash -docker run ... +docker run -p 80:80 -p 443:443 -v /path/to/website_files:/var/www/html -v /path/to/nginx_config:/etc/nginx/conf.d/ ctrlpanel/ctrlpanel ``` -This command will pull the latest CtrlPanel Docker image from Docker Hub and run it. +This command will run the latest CtrlPanel Docker image from Docker Hub and run it. The control panel will be available at http://localhost/install and will be a completely fresh installation. Note that while the container contains the full CtrlPanel installation, you will still need to perform the basic setup. You can find instructions for this [here](https://ctrlpanel.gg/docs/Installation/getting-started#basic-setup). -## 🏗️ Advanced Docker +## 🏗️ Migrating from a previous bare metal installation + +If you are migrating, from a previous bare metal installation, you can follow the instructions [here]() (Soon on documentation). + +## 🧰 Creating your own Docker image -If you are migrating, or want to create your own Docker image, you can follow the instructions [here](). +If you want to create your own Docker image, you can follow the instructions [here]() (Soon on documentation). From 27cad9173eb351efe3ebd39d27bc2b8522aaa2fd Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 14:34:19 +0200 Subject: [PATCH 294/514] FIX: standalone dockerfile now expose port 443 --- docker/standalone/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index 62957baf0..d59ca6947 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -37,6 +37,7 @@ RUN mkdir -p /var/log/nginx && \ # Expose ports EXPOSE 80 +EXPOSE 443 # Copy startup script COPY ./docker/standalone/scripts/startup.sh /usr/local/bin/startup-script.sh From 84417a9f6119f8017c04a2d1d07336a1248a29e7 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 7 May 2024 23:36:45 +0200 Subject: [PATCH 295/514] UPDATE: added redis php extention to standalone docker --- docker/standalone/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index d59ca6947..5c4a0c277 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -10,8 +10,9 @@ RUN apt-get update && apt-get install -y \ libpng-dev \ && rm -rf /var/lib/apt/lists/* -# Install PHP extensions -RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip gd bcmath +# Install PHP extensions including Redis +RUN pecl install redis && docker-php-ext-enable redis \ + && docker-php-ext-install mysqli pdo pdo_mysql intl zip gd bcmath # Copy the custom PHP-FPM configuration ADD ./docker/standalone/php/www.conf /usr/local/etc/php-fpm.d/ From 7d191efa1b2b5d225aad69e39de05449dd70098c Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 10 May 2024 18:36:35 +0200 Subject: [PATCH 296/514] ADD: some permission to files --- docker/standalone/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index 5c4a0c277..2477e2337 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -36,6 +36,9 @@ COPY ./docker/standalone/nginx/nginx.conf /etc/nginx/nginx.conf RUN mkdir -p /var/log/nginx && \ chown -R laravel:laravel /var/log/nginx +# Set permissions for Laravel directories +RUN chown -R www-data:www-data /var/default/storage /var/default/bootstrap/cache + # Expose ports EXPOSE 80 EXPOSE 443 From a7e5ab29bdc5e340d55e9decd4b4afc1c31e97e9 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 10 May 2024 18:37:01 +0200 Subject: [PATCH 297/514] ADD: standalone docker Queue Worker --- docker/standalone/scripts/startup.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 942e9f8e1..1c0de62ca 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -16,6 +16,9 @@ if [ -z "$(ls -A /var/www/html)" ] || [ ! -f "/var/www/html/.env" ]; then fi fi +# Start the queue worker service +php /var/www/html/artisan queue:work --sleep=3 --tries=3 & + # Start Nginx service nginx start From 0683e2d62bc9db5c7204ca31d30e105b7b2eb365 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 13 May 2024 10:29:24 +0200 Subject: [PATCH 298/514] ADD: nano to the standalone docker --- docker/standalone/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index 2477e2337..f0bf06272 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -3,6 +3,7 @@ FROM php:8.1-fpm # Install Nginx and other dependencies RUN apt-get update && apt-get install -y \ nginx \ + nano \ curl \ libcurl4-openssl-dev \ libicu-dev \ From 26a279fcb815e2d4b31abf20ddbb0566d1ccc494 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Mon, 13 May 2024 10:31:26 +0200 Subject: [PATCH 299/514] FIX: tried to fix the standalone docker permissions --- docker/standalone/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index f0bf06272..bed185d42 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -37,8 +37,8 @@ COPY ./docker/standalone/nginx/nginx.conf /etc/nginx/nginx.conf RUN mkdir -p /var/log/nginx && \ chown -R laravel:laravel /var/log/nginx -# Set permissions for Laravel directories -RUN chown -R www-data:www-data /var/default/storage /var/default/bootstrap/cache +# Set permissions (i don't know why but this is necessary, otherwise the panel won't work properly) +RUN chown -R 777 /var/default/ # Expose ports EXPOSE 80 From 00397c1dc8eb8d959cf1b9c64fa18c8f6af644a7 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 14 May 2024 14:47:27 +0200 Subject: [PATCH 300/514] FIX: logging config permissions --- config/logging.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/logging.php b/config/logging.php index 5aa1dbb78..5d4134ffa 100644 --- a/config/logging.php +++ b/config/logging.php @@ -61,6 +61,7 @@ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), + 'permission' => 0664, ], 'daily' => [ @@ -68,6 +69,7 @@ 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, + 'permission' => 0664, ], 'slack' => [ @@ -116,6 +118,7 @@ 'emergency' => [ 'path' => storage_path('logs/laravel.log'), + 'permission' => 0664, ], ], From e3957bc1924ee9eca685dba502e7b24e3dd9fa5d Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 14 May 2024 14:52:37 +0200 Subject: [PATCH 301/514] UPDATE: made a lot of change to the standalone dockerfile and script --- docker/standalone/Dockerfile | 66 ++++++++++++++++------------ docker/standalone/scripts/startup.sh | 55 ++++++++++++++++++----- 2 files changed, 82 insertions(+), 39 deletions(-) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index bed185d42..a16bed8cb 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -1,54 +1,62 @@ FROM php:8.1-fpm -# Install Nginx and other dependencies -RUN apt-get update && apt-get install -y \ +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ nginx \ nano \ curl \ + git \ libcurl4-openssl-dev \ libicu-dev \ libzip-dev \ libpng-dev \ && rm -rf /var/lib/apt/lists/* -# Install PHP extensions including Redis -RUN pecl install redis && docker-php-ext-enable redis \ - && docker-php-ext-install mysqli pdo pdo_mysql intl zip gd bcmath - -# Copy the custom PHP-FPM configuration -ADD ./docker/standalone/php/www.conf /usr/local/etc/php-fpm.d/ - -# Create directory for application and set ownership -RUN mkdir -p /var/default && \ - groupadd -g 1000 laravel && \ - useradd -u 1000 -g laravel -ms /bin/bash laravel && \ - chown laravel:laravel /var/default +# Install PHP extensions +RUN pecl install redis \ + && docker-php-ext-enable redis \ + && docker-php-ext-install -j$(nproc) \ + mysqli \ + pdo \ + pdo_mysql \ + intl \ + zip \ + gd \ + bcmath \ + && rm -rf /tmp/* /var/cache/* + +# Install Composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer -# Copy application files into the container -COPY . /var/default +# Create directory for application +RUN mkdir -p /var/default /var/www/html -# Copy Composer binary from Composer image -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +# Create user and group for Laravel +RUN groupadd -g 1000 laravel \ + && useradd -u 1000 -g laravel -ms /bin/bash laravel \ + && chown -R laravel:laravel /var/default /var/www/html -# Configure Nginx -COPY ./docker/standalone/nginx/nginx.conf /etc/nginx/nginx.conf +# Copy application files +COPY --chown=laravel:laravel . /var/default -# Create directory for Nginx logs and set ownership -RUN mkdir -p /var/log/nginx && \ - chown -R laravel:laravel /var/log/nginx +# Copy PHP-FPM configuration +COPY --chown=laravel:laravel ./docker/standalone/php/www.conf /usr/local/etc/php-fpm.d/ -# Set permissions (i don't know why but this is necessary, otherwise the panel won't work properly) -RUN chown -R 777 /var/default/ +# Copy Nginx configuration +COPY --chown=laravel:laravel ./docker/standalone/nginx/nginx.conf /etc/nginx/nginx.conf +# Create directory for Nginx logs +RUN mkdir -p /var/log/nginx && chown -R laravel:laravel /var/log/nginx # Expose ports -EXPOSE 80 -EXPOSE 443 +EXPOSE 80 443 # Copy startup script -COPY ./docker/standalone/scripts/startup.sh /usr/local/bin/startup-script.sh - +COPY --chown=laravel:laravel ./docker/standalone/scripts/startup.sh /usr/local/bin/startup-script.sh # Make startup script executable RUN chmod +x /usr/local/bin/startup-script.sh +# Set the working directory +WORKDIR /var/www/html + # Start the startup script CMD ["/usr/local/bin/startup-script.sh"] diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 1c0de62ca..5f36512d9 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -1,26 +1,61 @@ #!/bin/bash -# Check if /var/www/html is empty or .env file doesn't exist -if [ -z "$(ls -A /var/www/html)" ] || [ ! -f "/var/www/html/.env" ]; then +# Log directory +LOG_DIR="/var/www/html/storage/logs" + +echo "Starting script..." + +echo "Clearing log file..." +# clean all logs in log directory +if [ -n "$LOG_DIR" ]; then + rm -f "$LOG_DIR/startup-script.log" +fi + +# Check if log directory exists +if [ ! -d "$LOG_DIR" ]; then + echo "Warning: Log directory does not exist (maybe first install ?). Logging disabled until restart." + LOG_DIR="" +fi + +# Function to log messages +log_message() { + if [ -n "$LOG_DIR" ]; then + echo "$1" >> "$LOG_DIR/startup-script.log" + fi + echo "$1" +} + +# Check if project folder is empty. +if [ -z "$(ls -A /var/www/html)" ]; then + log_message "Warning: project folder is empty. Copying default files..." # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files + chown -R laravel:laravel /var/www/html/ + chmod -R 755 /var/www/html +fi - # Copy default Nginx configuration +# Check and copy default Nginx configuration if not exists +if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then + log_message "Warning: Nginx configuration not found. Copying default configuration..." cp -n /var/default/docker/standalone/nginx/default.conf /etc/nginx/conf.d/default.conf +fi - # Execute composer install if composer.json is present and there's no vendor directory - if [ -f "/var/www/html/composer.json" ] && [ ! -d "/var/www/html/vendor" ]; then - cd /var/www/html - composer install --no-dev --optimize-autoloader - cd - - fi +# Check and execute composer install if composer.json is present and there's no vendor directory +if [ -f "/var/www/html/composer.json" ] && [ ! -d "/var/www/html/vendor" ]; then + log_message "Warning: Composer dependencies not found. Running composer install..." + cd /var/www/html || exit + composer install --no-dev --optimize-autoloader + cd - || exit fi # Start the queue worker service -php /var/www/html/artisan queue:work --sleep=3 --tries=3 & +log_message "Starting the queue worker service..." +runuser -u laravel -- php /var/www/html/artisan queue:work --sleep=3 --tries=3 & # Start Nginx +log_message "Starting Nginx..." service nginx start # Start PHP-FPM +log_message "Starting PHP-FPM..." php-fpm -F From dc1eca34ef2652909e55cdb6105cc99f98f5adb9 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 14 May 2024 14:52:56 +0200 Subject: [PATCH 302/514] ADD: warning on top of the developement docker readme --- docker/development/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/development/README.md b/docker/development/README.md index 1581bd17a..1838becbc 100644 --- a/docker/development/README.md +++ b/docker/development/README.md @@ -1,3 +1,5 @@ +⚠ Caution: These instructions have not been updated since before version 1.0 of the project. Therefore, there may be inaccuracies, instability, or non-functional aspects. Proceed with care. (you may want to take a look at the standalone docker instread) + # Building the development environment cd into the project directory and run the following command: `sh bin/startdocker.sh` @@ -31,5 +33,3 @@ php artisan migrate:fresh --seed --env=testing Now when running tests with PHPUnit it will use your testing database and not your local development one. This is configured in the __phpunit.xml__. You can run your tests by running the command like this. Just type and enter. `php artisan test`. - - From 1c30f1310d16a0e3cc3cbce6dc86633b9027a636 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 14 May 2024 15:03:06 +0200 Subject: [PATCH 303/514] ADD: redis to php extention check of the panel installer --- public/install/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/install/functions.php b/public/install/functions.php index a5f2b0454..01dbcd04d 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -15,7 +15,7 @@ (new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); -$required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl']; +$required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl', 'redis']; $requirements = [ 'minPhp' => '8.1', From 98ce2e331501d6b3459e20c8df86b5e4b87a3855 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 14 May 2024 15:10:03 +0200 Subject: [PATCH 304/514] FIX: trusted proxy depreciated issue --- config/trustedproxy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/trustedproxy.php b/config/trustedproxy.php index dc46c31ba..f3b88cd52 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -26,7 +26,7 @@ * subsequently passed through. */ 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? - env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', '')), /* * Or, to trust all proxies that connect From cea4e9efd7b936f743ed0a43531cd816793dbac0 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 16 May 2024 13:14:55 +0200 Subject: [PATCH 305/514] implementing a mechanism to detect the absence of install.lock and redirecting users to /install to initiate the installation process. --- .gitignore | 1 + app/Http/Kernel.php | 2 ++ app/Http/Middleware/InstallerLock.php | 24 ++++++++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 app/Http/Middleware/InstallerLock.php diff --git a/.gitignore b/.gitignore index f72249a83..feb1500a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Ignore dependencies and cache +/.idea /node_modules /vendor /storage/*.key diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d0b1c7f7e..96815f452 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -4,6 +4,7 @@ use App\Http\Middleware\ApiAuthToken; use App\Http\Middleware\CheckSuspended; +use App\Http\Middleware\InstallerLock; use App\Http\Middleware\isAdmin; use App\Http\Middleware\isMod; use App\Http\Middleware\LastSeen; @@ -36,6 +37,7 @@ class Kernel extends HttpKernel */ protected $middlewareGroups = [ 'web' => [ + InstallerLock::class, \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, diff --git a/app/Http/Middleware/InstallerLock.php b/app/Http/Middleware/InstallerLock.php new file mode 100644 index 000000000..9390a598c --- /dev/null +++ b/app/Http/Middleware/InstallerLock.php @@ -0,0 +1,24 @@ +<?php + +namespace App\Http\Middleware; + +use Closure; +use Illuminate\Http\Request; + +class InstallerLock +{ + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next + * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse + */ + public function handle(Request $request, Closure $next) + { + if (!file_exists(base_path()."/install.lock")){ + return redirect('/install'); + } + return $next($request); + } +} From d13365e856f8d61a4b4cd1d7853fbed3512680cd Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 16 May 2024 13:22:44 +0200 Subject: [PATCH 306/514] Remove unused Middlewares --- app/Http/Kernel.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 96815f452..cd7cef5fb 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -5,8 +5,6 @@ use App\Http\Middleware\ApiAuthToken; use App\Http\Middleware\CheckSuspended; use App\Http\Middleware\InstallerLock; -use App\Http\Middleware\isAdmin; -use App\Http\Middleware\isMod; use App\Http\Middleware\LastSeen; use Illuminate\Foundation\Http\Kernel as HttpKernel; @@ -72,8 +70,6 @@ class Kernel extends HttpKernel 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - 'admin' => isAdmin::class, - 'moderator' => isMod::class, 'api.token' => ApiAuthToken::class, 'checkSuspended' => CheckSuspended::class, 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, From ddb628a34c957965a77a3fda14f4ad96b8382f8a Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 17 May 2024 14:35:17 +0200 Subject: [PATCH 307/514] FIX: csrf forms missing the hidden inputs --- themes/BlueInfinity/views/layouts/main.blade.php | 3 +++ .../views/admin/activitylogs/index.blade.php | 2 ++ themes/default/views/admin/api/create.blade.php | 2 ++ themes/default/views/admin/api/edit.blade.php | 2 ++ .../default/views/admin/coupons/create.blade.php | 2 ++ themes/default/views/admin/coupons/edit.blade.php | 2 ++ themes/default/views/admin/legal/index.blade.php | 2 ++ .../default/views/admin/partners/create.blade.php | 2 ++ .../default/views/admin/partners/edit.blade.php | 2 ++ .../default/views/admin/products/create.blade.php | 2 ++ .../default/views/admin/products/edit.blade.php | 2 ++ themes/default/views/admin/roles/edit.blade.php | 3 ++- themes/default/views/admin/servers/edit.blade.php | 2 ++ .../default/views/admin/settings/index.blade.php | 4 ++++ themes/default/views/admin/store/create.blade.php | 2 ++ themes/default/views/admin/store/edit.blade.php | 2 ++ .../views/admin/ticket/blacklist.blade.php | 5 +++-- .../default/views/admin/ticket/category.blade.php | 7 +++++-- .../views/admin/usefullinks/create.blade.php | 3 ++- .../views/admin/usefullinks/edit.blade.php | 2 ++ themes/default/views/admin/users/edit.blade.php | 15 +++++++++------ .../views/admin/users/notifications.blade.php | 2 ++ .../default/views/admin/vouchers/create.blade.php | 2 ++ .../default/views/admin/vouchers/edit.blade.php | 2 ++ themes/default/views/auth/login.blade.php | 2 ++ .../views/auth/passwords/confirm.blade.php | 2 ++ .../default/views/auth/passwords/email.blade.php | 1 + .../default/views/auth/passwords/reset.blade.php | 2 ++ themes/default/views/auth/register.blade.php | 6 ++++-- themes/default/views/auth/verify.blade.php | 2 ++ themes/default/views/layouts/main.blade.php | 3 +++ themes/default/views/profile/index.blade.php | 3 ++- themes/default/views/servers/create.blade.php | 1 + themes/default/views/servers/settings.blade.php | 10 ++++++---- themes/default/views/store/checkout.blade.php | 2 ++ themes/default/views/ticket/create.blade.php | 2 ++ 36 files changed, 91 insertions(+), 19 deletions(-) diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index b6549a7a0..71de7c9f7 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -85,6 +85,7 @@ class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> </button> @endforeach + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </li> @@ -190,6 +191,8 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i> {{ __('Logout') }} </button> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </li> diff --git a/themes/default/views/admin/activitylogs/index.blade.php b/themes/default/views/admin/activitylogs/index.blade.php index fda21cf8e..57cc4015b 100644 --- a/themes/default/views/admin/activitylogs/index.blade.php +++ b/themes/default/views/admin/activitylogs/index.blade.php @@ -56,6 +56,8 @@ <button class="btn btn-light btn-sm" type="submit"><i class="fa fa-search"></i></button> </div> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/api/create.blade.php b/themes/default/views/admin/api/create.blade.php index e0025e859..24fba4459 100644 --- a/themes/default/views/admin/api/create.blade.php +++ b/themes/default/views/admin/api/create.blade.php @@ -50,6 +50,8 @@ class="form-control @error('memo') is-invalid @enderror"> {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/api/edit.blade.php b/themes/default/views/admin/api/edit.blade.php index f99e0b278..e9ebdc669 100644 --- a/themes/default/views/admin/api/edit.blade.php +++ b/themes/default/views/admin/api/edit.blade.php @@ -51,6 +51,8 @@ class="form-control @error('memo') is-invalid @enderror"> {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/coupons/create.blade.php b/themes/default/views/admin/coupons/create.blade.php index 7cef744e7..765a4b4f4 100644 --- a/themes/default/views/admin/coupons/create.blade.php +++ b/themes/default/views/admin/coupons/create.blade.php @@ -233,6 +233,8 @@ class="input-group-append" {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/coupons/edit.blade.php b/themes/default/views/admin/coupons/edit.blade.php index aa3c81eec..e527e9d62 100644 --- a/themes/default/views/admin/coupons/edit.blade.php +++ b/themes/default/views/admin/coupons/edit.blade.php @@ -234,6 +234,8 @@ class="input-group-append" {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/legal/index.blade.php b/themes/default/views/admin/legal/index.blade.php index 5fb6025f9..d6120eeb7 100644 --- a/themes/default/views/admin/legal/index.blade.php +++ b/themes/default/views/admin/legal/index.blade.php @@ -96,6 +96,8 @@ class="form-control @error('imprint') is-invalid @enderror"> <div class="row"> <button class="btn btn-primary ml-3 mt-3">{{ __('Save') }}</button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> <!-- END CUSTOM CONTENT --> diff --git a/themes/default/views/admin/partners/create.blade.php b/themes/default/views/admin/partners/create.blade.php index 03f8cab31..6d3aad1e9 100644 --- a/themes/default/views/admin/partners/create.blade.php +++ b/themes/default/views/admin/partners/create.blade.php @@ -106,6 +106,8 @@ class="form-control @error('referral_system_commission') is-invalid @enderror"> {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/partners/edit.blade.php b/themes/default/views/admin/partners/edit.blade.php index 289edf5ba..3c87549c2 100644 --- a/themes/default/views/admin/partners/edit.blade.php +++ b/themes/default/views/admin/partners/edit.blade.php @@ -113,6 +113,8 @@ class="form-control @error('referral_system_commission') is-invalid @enderror"> {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index c6c5427f6..d93bd4023 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -340,6 +340,8 @@ class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" </div> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index 8f7649580..4546730c7 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -343,6 +343,8 @@ class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" </div> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </section> diff --git a/themes/default/views/admin/roles/edit.blade.php b/themes/default/views/admin/roles/edit.blade.php index 91fef77df..600fcd093 100644 --- a/themes/default/views/admin/roles/edit.blade.php +++ b/themes/default/views/admin/roles/edit.blade.php @@ -53,6 +53,8 @@ <div class="form-group d-flex justify-content-end mt-3"> <button name="submit" type="submit" class="btn btn-primary">{{__('Submit')}}</button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> @@ -65,4 +67,3 @@ }) </script> @endsection - diff --git a/themes/default/views/admin/servers/edit.blade.php b/themes/default/views/admin/servers/edit.blade.php index f9afd2b28..db3dc8925 100644 --- a/themes/default/views/admin/servers/edit.blade.php +++ b/themes/default/views/admin/servers/edit.blade.php @@ -80,6 +80,8 @@ class="fas fa-info-circle"></i> <button type="submit" class="btn btn-primary">{{ __('Submit') }}</button> + <input type="hidden" name="_token" value="{{ csrf_token() }}"> + </form> </div> </div> </div> diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 799e426de..c023977e2 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -157,6 +157,8 @@ class="form-control" name="logo" id="logo"> <div class="row"> <button class="mt-3 ml-3 btn btn-primary">{{ __('Save') }}</button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> @foreach ($settings as $category => $options) @@ -307,6 +309,8 @@ class="float-right ml-2 btn btn-secondary">Reset </button> </div> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> @endcanany diff --git a/themes/default/views/admin/store/create.blade.php b/themes/default/views/admin/store/create.blade.php index 85b3b18df..91acc0b44 100644 --- a/themes/default/views/admin/store/create.blade.php +++ b/themes/default/views/admin/store/create.blade.php @@ -152,6 +152,8 @@ class="form-control @error('description') is-invalid @enderror" {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/store/edit.blade.php b/themes/default/views/admin/store/edit.blade.php index 9e8ff2303..3dc469213 100644 --- a/themes/default/views/admin/store/edit.blade.php +++ b/themes/default/views/admin/store/edit.blade.php @@ -142,6 +142,8 @@ class="form-control @error('description') is-invalid @enderror" required="requir {{ __('Submit') }} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/ticket/blacklist.blade.php b/themes/default/views/admin/ticket/blacklist.blade.php index f501d3995..ac47b456b 100644 --- a/themes/default/views/admin/ticket/blacklist.blade.php +++ b/themes/default/views/admin/ticket/blacklist.blade.php @@ -61,7 +61,7 @@ class="fas fa-info-circle"></i></h5> </div> <div class="card-body"> <form action="{{route('admin.ticket.blacklist.add')}}" method="POST" class="ticket-form"> - @csrf + @csrf <div class="p-0 mb-3 custom-control"> <label for="user_id">{{ __('User') }}: <i data-toggle="popover" data-trigger="hover" @@ -78,6 +78,8 @@ class="fas fa-info-circle"></i></h5> <button type="submit" class="btn btn-primary ticket-once"> {{__('Submit')}} </button> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> @@ -179,4 +181,3 @@ function escapeHtml(str) { }); </script> @endsection - diff --git a/themes/default/views/admin/ticket/category.blade.php b/themes/default/views/admin/ticket/category.blade.php index 42524285e..04414502e 100644 --- a/themes/default/views/admin/ticket/category.blade.php +++ b/themes/default/views/admin/ticket/category.blade.php @@ -57,7 +57,7 @@ </div> <div class="card-body"> <form action="{{route("admin.ticket.category.store")}}" method="POST" class="ticket-form"> - @csrf + @csrf <div class="form-group "> <label for="name" class="control-label">{{__("Name")}}</label> <input id="name" type="text" class="form-control" name="name" required> @@ -65,6 +65,8 @@ <button type="submit" class="btn btn-primary"> {{__('Submit')}} </button> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> @@ -90,6 +92,8 @@ <button type="submit" class="btn btn-primary"> {{__('Submit')}} </button> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> @@ -129,4 +133,3 @@ </script> @endsection - diff --git a/themes/default/views/admin/usefullinks/create.blade.php b/themes/default/views/admin/usefullinks/create.blade.php index 2c09571a1..3eb63dc4b 100644 --- a/themes/default/views/admin/usefullinks/create.blade.php +++ b/themes/default/views/admin/usefullinks/create.blade.php @@ -34,7 +34,6 @@ <form action="{{route('admin.usefullinks.store')}}" method="POST"> @csrf - <div class="form-group"> <label for="icon">{{__('Icon class name')}}</label> <input value="{{old('icon')}}" id="icon" name="icon" @@ -117,6 +116,8 @@ class="form-control @error('description') is-invalid @enderror"> {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/usefullinks/edit.blade.php b/themes/default/views/admin/usefullinks/edit.blade.php index 72d8c9a69..acbfee4e3 100644 --- a/themes/default/views/admin/usefullinks/edit.blade.php +++ b/themes/default/views/admin/usefullinks/edit.blade.php @@ -118,6 +118,8 @@ class="form-control @error('description') is-invalid @enderror"> {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/users/edit.blade.php b/themes/default/views/admin/users/edit.blade.php index 221571918..a8a221ad8 100644 --- a/themes/default/views/admin/users/edit.blade.php +++ b/themes/default/views/admin/users/edit.blade.php @@ -106,7 +106,7 @@ class="custom-select @error('role') is-invalid @enderror" @endif value="{{$role->id}}">{{$role->name}}</option> @endforeach </select> - </div> + </div> </div> <div class="form-group"> <label for="name">{{__('Referral-Code')}}</label> @@ -118,15 +118,18 @@ class="form-control @error('referral_code') is-invalid @enderror" required="requ </div> @enderror </div> - @error('role') - <div class="text-danger"> - {{$message}} - </div> - @enderror + @error('role') + <div class="text-danger"> + {{$message}} + </div> + @enderror <div class="form-group text-right"> <button type="submit" class="btn btn-primary">{{__('Submit')}}</button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> + </form> </div> </div> </div> diff --git a/themes/default/views/admin/users/notifications.blade.php b/themes/default/views/admin/users/notifications.blade.php index e29303add..13290c6f4 100644 --- a/themes/default/views/admin/users/notifications.blade.php +++ b/themes/default/views/admin/users/notifications.blade.php @@ -104,6 +104,8 @@ class="form-control @error('content') is-invalid @enderror"> <div class="form-group text-right"> <button type="submit" class="btn btn-primary">{{__('Submit')}}</button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/vouchers/create.blade.php b/themes/default/views/admin/vouchers/create.blade.php index f5ba3591b..77a337f88 100644 --- a/themes/default/views/admin/vouchers/create.blade.php +++ b/themes/default/views/admin/vouchers/create.blade.php @@ -133,6 +133,8 @@ class="form-control @error('expires_at') is-invalid @enderror datetimepicker-inp {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/admin/vouchers/edit.blade.php b/themes/default/views/admin/vouchers/edit.blade.php index 92cfd0369..d145d0a72 100644 --- a/themes/default/views/admin/vouchers/edit.blade.php +++ b/themes/default/views/admin/vouchers/edit.blade.php @@ -136,6 +136,8 @@ class="form-control @error('expires_at') is-invalid @enderror datetimepicker-inp {{__('Submit')}} </button> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/auth/login.blade.php b/themes/default/views/auth/login.blade.php index c570550d9..323d03e41 100644 --- a/themes/default/views/auth/login.blade.php +++ b/themes/default/views/auth/login.blade.php @@ -93,6 +93,8 @@ class="form-control @error('password') is-invalid @enderror" </div> <!-- /.col --> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> <p class="mb-1"> @if (Route::has('password.request')) diff --git a/themes/default/views/auth/passwords/confirm.blade.php b/themes/default/views/auth/passwords/confirm.blade.php index ca78fc1d3..ec68ae653 100644 --- a/themes/default/views/auth/passwords/confirm.blade.php +++ b/themes/default/views/auth/passwords/confirm.blade.php @@ -40,6 +40,8 @@ @endif </div> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/auth/passwords/email.blade.php b/themes/default/views/auth/passwords/email.blade.php index 216b7ce3e..40f1d6764 100644 --- a/themes/default/views/auth/passwords/email.blade.php +++ b/themes/default/views/auth/passwords/email.blade.php @@ -56,6 +56,7 @@ class="btn btn-primary btn-block">{{ __('Request new password') }}</button> <!-- /.col --> </div> + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> <p class="mt-3 mb-1"> <a href="{{ route('login') }}">{{ __('Login') }}</a> diff --git a/themes/default/views/auth/passwords/reset.blade.php b/themes/default/views/auth/passwords/reset.blade.php index a2648b1d0..99b9f14d8 100644 --- a/themes/default/views/auth/passwords/reset.blade.php +++ b/themes/default/views/auth/passwords/reset.blade.php @@ -67,6 +67,8 @@ class="btn btn-primary btn-block">{{ __('Change password') }}</button> </div> <!-- /.col --> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> <p class="mt-3 mb-1"> diff --git a/themes/default/views/auth/register.blade.php b/themes/default/views/auth/register.blade.php index 6386371e1..02acc2470 100644 --- a/themes/default/views/auth/register.blade.php +++ b/themes/default/views/auth/register.blade.php @@ -154,8 +154,10 @@ class="form-control @error('email') is-invalid @enderror" <button type="submit" class="btn btn-primary">{{ __('Register') }}</button> </div> <!-- /.col --> - </div> - </form> + </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> + </form> <a href="{{ route('login') }}" class="text-center">{{ __('I already have a membership') }}</a> </div> <!-- /.form-box --> diff --git a/themes/default/views/auth/verify.blade.php b/themes/default/views/auth/verify.blade.php index 9f8c1bc0f..521400148 100644 --- a/themes/default/views/auth/verify.blade.php +++ b/themes/default/views/auth/verify.blade.php @@ -19,6 +19,8 @@ <form class="d-inline" method="POST" action="{{ route('verification.resend') }}"> @csrf <button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>. + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 9631ef64d..9a9b8022c 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -85,6 +85,7 @@ class="fab fa-discord mr-2"></i>{{ __('Discord') }}</a> </button> @endforeach + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </li> @@ -190,6 +191,8 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i> {{ __('Logout') }} </button> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </li> diff --git a/themes/default/views/profile/index.blade.php b/themes/default/views/profile/index.blade.php index 14117343d..628f8daf9 100644 --- a/themes/default/views/profile/index.blade.php +++ b/themes/default/views/profile/index.blade.php @@ -304,8 +304,9 @@ class="mr-1 fab fa-discord"></i>{{ __('Re-Sync Discord') }} </div> </div> </div> - </form> + <input type="hidden" name="_token" value="{{ csrf_token() }}"> + </form> </div> <!-- END CUSTOM CONTENT --> diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 588da5a40..c4c30f020 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -258,6 +258,7 @@ class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" </div> </div> + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> <!-- END FORM --> diff --git a/themes/default/views/servers/settings.blade.php b/themes/default/views/servers/settings.blade.php index 7aba57d01..ae659be2b 100644 --- a/themes/default/views/servers/settings.blade.php +++ b/themes/default/views/servers/settings.blade.php @@ -269,10 +269,11 @@ class="btn btn-info btn-md"> <br> <strong>{{__("Caution") }}:</strong> {{__("Upgrading/Downgrading your server will reset your billing cycle to now. Your overpayed Credits will be refunded. The price for the new billing cycle will be withdrawed")}}. <br> <br> {{__("Server will be automatically restarted once upgraded")}} - </div> - <div class="modal-footer card-body"> - <button type="submit" class="btn btn-primary upgrade-once" style="width: 100%"><strong>{{__("Change Product")}}</strong></button> - </div> + </div> + <div class="modal-footer card-body"> + <button type="submit" class="btn btn-primary upgrade-once" style="width: 100%"><strong>{{__("Change Product")}}</strong></button> + </div> + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> @@ -303,6 +304,7 @@ class="btn btn-danger btn-md"> @csrf @method('DELETE') <button data-toggle="popover" data-trigger="hover" data-placement="top" class="btn btn-danger mr-1">{{__("Delete")}}</button> + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </div> diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index b0f91f0de..f1f836c55 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -223,6 +223,8 @@ class="text-muted d-inline-block">{{ strtolower($product->type) == 'credits' ? $ </div> </div> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> diff --git a/themes/default/views/ticket/create.blade.php b/themes/default/views/ticket/create.blade.php index c25b20e6b..a8e1b1a20 100644 --- a/themes/default/views/ticket/create.blade.php +++ b/themes/default/views/ticket/create.blade.php @@ -132,6 +132,8 @@ </div> </div> </div> + + <input type="hidden" name="_token" value="{{ csrf_token() }}"> </form> </div> </section> From ab64fafbf2c30a0eaf0c8c33c15160af85654c95 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 17 May 2024 15:37:13 +0200 Subject: [PATCH 308/514] DELETE: unused / deprecated files --- .../default/views/admin/nests/index.blade.php | 100 ------------------ .../default/views/admin/nodes/index.blade.php | 100 ------------------ 2 files changed, 200 deletions(-) delete mode 100644 themes/default/views/admin/nests/index.blade.php delete mode 100644 themes/default/views/admin/nodes/index.blade.php diff --git a/themes/default/views/admin/nests/index.blade.php b/themes/default/views/admin/nests/index.blade.php deleted file mode 100644 index 152773e48..000000000 --- a/themes/default/views/admin/nests/index.blade.php +++ /dev/null @@ -1,100 +0,0 @@ -<!-- -THIS FILE IS DEPRECATED - --> - - -@extends('layouts.main') - -@section('content') - <!-- CONTENT HEADER --> - <section class="content-header"> - <div class="container-fluid"> - <div class="row mb-2"> - <div class="col-sm-6"> - <h1>{{__('Nests')}}</h1> - </div> - <div class="col-sm-6"> - <ol class="breadcrumb float-sm-right"> - <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> - <li class="breadcrumb-item"><a class="text-muted" - href="{{route('admin.nests.index')}}">{{__('Nests')}}</a></li> - </ol> - </div> - </div> - </div> - </section> - <!-- END CONTENT HEADER --> - - <!-- MAIN CONTENT --> - <section class="content"> - <div class="container-fluid"> - - <div class="card"> - - <div class="card-header"> - <div class="d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fa-sitemap mr-2"></i>{{__('Nests')}}</h5> - <a href="{{route('admin.nests.sync')}}" class="btn btn-sm btn-info"><i - class="fas fa-sync mr-1"></i>{{__('Sync')}}</a> - </div> - </div> - - <div class="card-body table-responsive"> - - <table id="datatable" class="table table-striped"> - <thead> - <tr> - <th>{{__('Active')}}</th> - <th>{{__('ID')}}</th> - <th>{{__('eggs')}}</th> - <th>{{__('Name')}}</th> - <th>{{__('Description')}}</th> - <th>{{__('Created at')}}</th> - - </tr> - </thead> - <tbody> - </tbody> - </table> - - </div> - </div> - - - </div> - <!-- END CUSTOM CONTENT --> - - </section> - <!-- END CONTENT --> - - <script> - function submitResult() { - return confirm({{__("Are you sure you wish to delete?")}}) !== false; - } - - document.addEventListener("DOMContentLoaded", function () { - $('#datatable').DataTable({ - language: { - url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("SETTINGS::LOCALE:DATATABLES")}}.json' - }, - processing: true, - serverSide: true, - stateSave: true, - ajax: "{{route('admin.nests.datatable')}}", - order: [[ 1, "desc" ]], - columns: [ - {data: 'actions', name : 'disabled'}, - {data: 'id'}, - {data: 'eggs' , sortable : false}, - {data: 'name' , name : 'nests.name'}, - {data: 'description'}, - {data: 'created_at'}, - - ] - }); - }); - </script> - - - -@endsection diff --git a/themes/default/views/admin/nodes/index.blade.php b/themes/default/views/admin/nodes/index.blade.php deleted file mode 100644 index bb57e0cee..000000000 --- a/themes/default/views/admin/nodes/index.blade.php +++ /dev/null @@ -1,100 +0,0 @@ -<!-- -THIS FILE IS DEPRECATED - --> - - -@extends('layouts.main') - -@section('content') - <!-- CONTENT HEADER --> - <section class="content-header"> - <div class="container-fluid"> - <div class="row mb-2"> - <div class="col-sm-6"> - <h1>{{__('Nodes')}}</h1> - </div> - <div class="col-sm-6"> - <ol class="breadcrumb float-sm-right"> - <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> - <li class="breadcrumb-item"><a class="text-muted" - href="{{route('admin.nodes.index')}}">{{__('Nodes')}}</a></li> - </ol> - </div> - </div> - </div> - </section> - <!-- END CONTENT HEADER --> - - <!-- MAIN CONTENT --> - <section class="content"> - <div class="container-fluid"> - - <div class="card"> - - <div class="card-header"> - <div class="d-flex justify-content-between"> - <h5 class="card-title"><i class="fas fa-sitemap mr-2"></i>{{__('Nodes')}}</h5> - <a href="{{route('admin.nodes.sync')}}" class="btn btn-sm btn-info"><i - class="fas fa-sync mr-1"></i>{{__('Sync')}}</a> - </div> - </div> - - <div class="card-body table-responsive"> - - <table id="datatable" class="table table-striped"> - <thead> - <tr> - <th>{{__('Active')}}</th> - <th>{{__('ID')}}</th> - <th>{{__('Location')}}</th> - <th>{{__('Name')}}</th> - <th>{{__('Description')}}</th> - <th>{{__('Created at')}}</th> - - </tr> - </thead> - <tbody> - </tbody> - </table> - - </div> - </div> - - - </div> - <!-- END CUSTOM CONTENT --> - - </section> - <!-- END CONTENT --> - - <script> - function submitResult() { - return confirm({{__("Are you sure you wish to delete?")}}) !== false; - } - - document.addEventListener("DOMContentLoaded", function () { - $('#datatable').DataTable({ - language: { - url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("SETTINGS::LOCALE:DATATABLES")}}.json' - }, - processing: true, - serverSide: true, - stateSave: true, - ajax: "{{route('admin.nodes.datatable')}}", - order: [[ 1, "desc" ]], - columns: [ - {data: 'actions', name : 'disabled'}, - {data: 'id'}, - {data: 'location' , name : 'location.name'}, - {data: 'name' , name : 'nodes.name'}, - {data: 'description'}, - {data: 'created_at'}, - - ] - }); - }); - </script> - - - -@endsection From be6c5d7addc299edde2e45d46d9d85ba44e69427 Mon Sep 17 00:00:00 2001 From: Vikas Dongre <zvikasdongre@gmail.com> Date: Mon, 20 May 2024 18:10:22 +0530 Subject: [PATCH 309/514] fix: pterodactyl settings not getting encrypted during installation Also fixes minor issues like spelling / sentence mistakes. Refactored some variable names, and made it actually show error message on the page instead of only in logs. Now email settings have select input for mail method and encryption. --- app/Console/Commands/GetSettingCommand.php | 49 +++++++++++++ app/Console/Commands/SetSettingCommand.php | 52 ++++++++++++++ public/install/forms.php | 84 +++++++++++----------- public/install/index.php | 58 +++++++++------ 4 files changed, 176 insertions(+), 67 deletions(-) create mode 100644 app/Console/Commands/GetSettingCommand.php create mode 100644 app/Console/Commands/SetSettingCommand.php diff --git a/app/Console/Commands/GetSettingCommand.php b/app/Console/Commands/GetSettingCommand.php new file mode 100644 index 000000000..9be2191ad --- /dev/null +++ b/app/Console/Commands/GetSettingCommand.php @@ -0,0 +1,49 @@ +<?php + +namespace App\Console\Commands; + +use Illuminate\Console\Command; + +class GetSettingCommand extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'settings:get {class : Settings Class (Example: GeneralSettings)} {key} {--sameline : Outputs the result without newline, useful for implementing in scripts.}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Gets value of a setting key and decrypts it if needed.'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + + $class = $this->argument('class'); + $key = $this->argument('key'); + $sameline = $this->option('sameline'); + + try { + $settings_class = "App\\Settings\\$class"; + $settings = new $settings_class(); + + $this->output->write($settings->$key, !$sameline); + + return Command::SUCCESS; + } catch (\Throwable $th) { + $this->error('Error: ' . $th->getMessage()); + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/app/Console/Commands/SetSettingCommand.php b/app/Console/Commands/SetSettingCommand.php new file mode 100644 index 000000000..03481f93e --- /dev/null +++ b/app/Console/Commands/SetSettingCommand.php @@ -0,0 +1,52 @@ +<?php + +namespace App\Console\Commands; + +use Exception; +use Illuminate\Console\Command; + +class SetSettingCommand extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'settings:set {class : Settings Class (Example: GeneralSettings)} {key : Unique setting key} {value : Value to set}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Set value of a setting key.'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle() + { + + $class = $this->argument('class'); + $key = $this->argument('key'); + $value = $this->argument('value'); + + try { + $settings_class = "App\\Settings\\$class"; + $settings = new $settings_class(); + + $settings->$key = $value; + + $settings->save(); + + $this->info("Successfully updated '$key'."); + } catch (\Throwable $th) { + $this->error('Error: ' . $th->getMessage()); + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/public/install/forms.php b/public/install/forms.php index 96f1dfa17..9bed6eda2 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -97,27 +97,30 @@ $mail = new PHPMailer(true); //Server settings - $mail->isSMTP(); // Send using SMTP - $mail->Host = $_POST['host']; // Set the SMTP server to send through - $mail->SMTPAuth = true; // Enable SMTP authentication - $mail->Username = $_POST['user']; // SMTP username - $mail->Password = $_POST['pass']; // SMTP password - $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // Enable TLS encryption; `PHPMailer::ENCRYPTION_SMTPS` encouraged - $mail->Port = $_POST['port']; // TCP port to connect to, use 465 for `PHPMailer::ENCRYPTION_SMTPS` - - //Recipients + // Send using SMTP + $mail->isSMTP(); + $mail->Host = $_POST['host']; + // Enable SMTP authentication + $mail->SMTPAuth = true; + $mail->Username = $_POST['user']; + $mail->Password = $_POST['pass']; + $mail->SMTPSecure = $_POST['encryption']; + $mail->Port = (int) $_POST['port']; + + // Test E-mail metadata $mail->setFrom($_POST['user'], $_POST['user']); - $mail->addAddress($_POST['user'], $_POST['user']); // Add a recipient + $mail->addAddress($_POST['user'], $_POST['user']); // Content - $mail->isHTML(true); // Set email format to HTML - $mail->Subject = 'It Worked!'; + // Set email format to HTML + $mail->isHTML(true); + $mail->Subject = 'It Worked! - Test E-Mail from Ctrlpanel.gg'; $mail->Body = 'Your E-Mail Settings are correct!'; $mail->send(); } catch (Exception $e) { wh_log($mail->ErrorInfo, 'error'); - header('LOCATION: index.php?step=4&message=Something wasnt right when sending the E-Mail!'); + header('LOCATION: index.php?step=4&message=Something went wrong while sending test E-Mail!<br>' . $mail->ErrorInfo); exit(); } @@ -140,8 +143,7 @@ ]; foreach ($values as $key => $value) { - $query = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '$value' WHERE `name` = '$key' AND `group` = 'mail'"; - $db->query($query); + run_console("php artisan settings:set 'MailSettings' '$key' '$value'"); } wh_log('Database updated', 'debug'); @@ -197,34 +199,22 @@ exit(); } else { wh_log('Pterodactyl Settings are correct', 'debug'); - wh_log('Updating Database', 'debug'); - $key = $key; - $clientkey = $clientkey; - - $query1 = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '" . json_encode($url) . "' WHERE (`name` = 'panel_url' AND `group` = 'pterodactyl')"; - $query2 = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '" . json_encode($key) . "' WHERE (`name` = 'admin_token' AND `group` = 'pterodactyl')"; - $query3 = 'UPDATE `' . getenv('DB_DATABASE') . "`.`settings` SET `payload` = '" . json_encode($clientkey) . "' WHERE (`name` = 'user_token' AND `group` = 'pterodactyl')"; - - $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); - if ($db->connect_error) { - wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=5&message=Could not connect to the Database'); - exit(); - } - - if ($db->query($query1) && $db->query($query2) && $db->query($query3)) { + try { + run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'"); + run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); + run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); wh_log('Database updated', 'debug'); header('LOCATION: index.php?step=6'); - } else { - wh_log($db->error, 'error'); - header('LOCATION: index.php?step=5&message=Something went wrong when communicating with the Database!'); + } catch (\Throwable $th) { + wh_log("Setting Pterodactyl information failed.", 'error'); + header("LOCATION: index.php?step=5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); } } } if (isset($_POST['createUser'])) { - wh_log('Creating User', 'debug'); + wh_log('Getting Pterodactyl User', 'debug'); $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); if ($db->connect_error) { wh_log($db->connect_error, 'error'); @@ -236,19 +226,26 @@ $pass = $_POST['pass']; $repass = $_POST['repass']; - $key = $db->query('SELECT `payload` FROM `' . getenv('DB_DATABASE') . "`.`settings` WHERE `name` = 'admin_token' AND `group` = 'pterodactyl'")->fetch_assoc(); - $key = removeQuotes($key['payload']); - $pterobaseurl = $db->query('SELECT `payload` FROM `' . getenv('DB_DATABASE') . "`.`settings` WHERE `name` = 'panel_url' AND `group` = 'pterodactyl'")->fetch_assoc(); + try { + $panel_url = run_console("php artisan settings:get 'PterodactylSettings' 'panel_url' --sameline"); + $admin_token = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); + wh_log('Database updated', 'debug'); + header('LOCATION: index.php?step=6'); + } catch (\Throwable $th) { + wh_log("Getting Pterodactyl information failed.", 'error'); + header("LOCATION: index.php?step=5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + } + + $panel_api_url = $panel_url . '/api/application/users/' . $pteroID; - $pteroURL = removeQuotes($pterobaseurl['payload']) . '/api/application/users/' . $pteroID; $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $pteroURL); + curl_setopt($ch, CURLOPT_URL, $panel_api_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', 'Content-Type: application/json', - 'Authorization: Bearer ' . $key, + 'Authorization: Bearer ' . $admin_token, ]); $response = curl_exec($ch); $result = json_decode($response, true); @@ -267,15 +264,14 @@ $name = $result['attributes']['username']; $pass = password_hash($pass, PASSWORD_DEFAULT); - $pteroURL = removeQuotes($pterobaseurl['payload']) . '/api/application/users/' . $pteroID; $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $pteroURL); + curl_setopt($ch, CURLOPT_URL, $panel_api_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', 'Content-Type: application/json', - 'Authorization: Bearer ' . $key, + 'Authorization: Bearer ' . $admin_token, ]); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'email' => $mail, diff --git a/public/install/index.php b/public/install/index.php index 6afd2e70c..8d63f71d9 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -76,29 +76,36 @@ function cardStart($title, $subtitle = null) <li class="<?php echo checkWriteable() == true ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> - <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> php - version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>)</li> + <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) + </li> - <li class="<?php echo getMySQLVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> mysql - version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>)</li> + <li class="<?php echo getMySQLVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + mysql version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>) + </li> - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> Missing - php-extentions: <?php echo count(checkExtensions()) == 0 ? 'none' : ''; - foreach (checkExtensions() as $ext) { - echo $ext . ', '; - } - - echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?></li> + <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> + Missing php-extentions: + <?php echo count(checkExtensions()) == 0 ? 'none' : ''; + foreach (checkExtensions() as $ext) { + echo $ext . ', '; + } + echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?> + </li> <!-- <li class="<?php echo getZipVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Zip version: <?php echo getZipVersion(); ?> </li> --> - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Git - version: <?php echo getGitVersion(); ?> </li> + <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Git version: + <?php echo getGitVersion(); ?> + </li> - <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Tar - version: <?php echo getTarVersion(); ?> </li> + <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Tar version: + <?php echo getTarVersion(); ?> + </li> </ul> </div> @@ -143,7 +150,7 @@ function cardStart($title, $subtitle = null) <div class="form-group"> <div class="flex flex-col mb-3"> <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="controlpaneluser" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="ctrlpaneluser" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> <div class="form-group"> @@ -156,7 +163,7 @@ function cardStart($title, $subtitle = null) <div class="form-group"> <div class="flex flex-col"> <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="controlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> @@ -213,8 +220,8 @@ function cardStart($title, $subtitle = null) </div> <div class="form-group"> <div class="flex flex-col"> - <label for="name">Host Name</label> - <input id="name" name="name" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <label for="name">Dashboard Name</label> + <input id="name" name="name" type="text" required value="CtrlPanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> @@ -248,8 +255,9 @@ function cardStart($title, $subtitle = null) <div class="form-group"> <div class="flex flex-col mb-3"> <label for="method">Your E-Mail Method</label> - <input id="method" name="method" type="text" required value="smtp" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - + <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="smtp" selected>SMTP</option> + </select> </div> </div> <div class="form-group"> @@ -284,7 +292,11 @@ function cardStart($title, $subtitle = null) <div class="form-group"> <div class="flex flex-col"> <label for="encryption">Your Mail encryption method</label> - <input id="encryption" name="encryption" type="text" required value="tls" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <select id="encryption" name="encryption" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="tls" selected>TLS</option> + <option value="ssl">SSL</option> + <option value="null">None</option> + </select> </div> </div> @@ -431,4 +443,4 @@ function cardStart($title, $subtitle = null) ?> </body> -</html> +</html> \ No newline at end of file From 487fbe0427de5f607f7f99856dbfc43e4dad3cd7 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 22 May 2024 11:25:39 +0200 Subject: [PATCH 310/514] Should fix ticket 429 too many requests --- .gitignore | 3 +++ app/Http/Controllers/TicketsController.php | 11 ++++++++++- routes/web.php | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index f72249a83..0d6b6034b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ Homestead.yaml public/install/logs.txt install.lock public/install/logs/installer.log + +/.idea +cpggdatabase.sql diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index de59bf883..d55516fca 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -17,6 +17,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Notification; +use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Str; class TicketsController extends Controller @@ -35,6 +36,9 @@ public function index(LocaleSettings $locale_settings, TicketSettings $ticketSet public function store(Request $request, TicketSettings $ticket_settings) { + if (RateLimiter::tooManyAttempts('ticket-send:'.Auth::user()->id, $perMinute = 1)) { + return redirect()->back()->with('error', __('Please wait before creating a new Ticket')); + } $this->validate( $request, [ @@ -67,6 +71,7 @@ public function store(Request $request, TicketSettings $ticket_settings) $user->notify(new CreateNotification($ticket)); + RateLimiter::hit('ticket-send:'.Auth::user()->id); return redirect()->route('ticket.index')->with('success', __('A ticket has been opened, ID: #') . $ticket->ticket_id); } @@ -89,6 +94,9 @@ public function show($ticket_id, PterodactylSettings $ptero_settings) public function reply(Request $request) { + if (RateLimiter::tooManyAttempts('ticket-reply:'.Auth::user()->id, $perMinute = 1)) { + return redirect()->back()->with('error', __('Please wait before answering a Ticket')); + } //check in blacklist $check = TicketBlacklist::where('user_id', Auth::user()->id)->first(); if ($check && $check->status == 'True') { @@ -101,6 +109,7 @@ public function reply(Request $request) return redirect()->back()->with('warning', __('Ticket not found on the server. It potentially got deleted earlier')); } $ticket->status = 'Client Reply'; + $ticket->updated_at = now(); $ticket->update(); $ticketcomment = TicketComment::create([ 'ticket_id' => $request->input('ticket_id'), @@ -115,7 +124,7 @@ public function reply(Request $request) foreach($staffNotify as $staff){ Notification::send($staff, new AdminReplyNotification($ticket, $user, $newmessage)); } - + RateLimiter::hit('ticket-reply:'.Auth::user()->id); return redirect()->back()->with('success', __('Your comment has been submitted')); } diff --git a/routes/web.php b/routes/web.php index c575ea981..f5078e3c1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -119,9 +119,9 @@ Route::get('ticket', [TicketsController::class, 'index'])->name('ticket.index'); Route::get('ticket/datatable', [TicketsController::class, 'datatable'])->name('ticket.datatable'); Route::get('ticket/new', [TicketsController::class, 'create'])->name('ticket.new'); - Route::post('ticket/new', [TicketsController::class, 'store'])->middleware(['throttle:ticket-new'])->name('ticket.new.store'); + Route::post('ticket/new', [TicketsController::class, 'store'])->name('ticket.new.store'); Route::get('ticket/show/{ticket_id}', [TicketsController::class, 'show'])->name('ticket.show'); - Route::post('ticket/reply', [TicketsController::class, 'reply'])->middleware(['throttle:ticket-reply'])->name('ticket.reply'); + Route::post('ticket/reply', [TicketsController::class, 'reply'])->name('ticket.reply'); Route::post('ticket/status/{ticket_id}', [TicketsController::class, 'changeStatus'])->name('ticket.changeStatus'); From eeabc162876f82a283fca40f35538aad273c2323 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Wed, 22 May 2024 14:24:33 +0200 Subject: [PATCH 311/514] FIX: recaptcha error wen creating a tickets --- app/Http/Controllers/TicketsController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index d55516fca..c2c9d14d4 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -34,7 +34,7 @@ public function index(LocaleSettings $locale_settings, TicketSettings $ticketSet ]); } - public function store(Request $request, TicketSettings $ticket_settings) + public function store(Request $request, TicketSettings $ticket_settings, GeneralSettings $generalSettings) { if (RateLimiter::tooManyAttempts('ticket-send:'.Auth::user()->id, $perMinute = 1)) { return redirect()->back()->with('error', __('Please wait before creating a new Ticket')); @@ -46,7 +46,7 @@ public function store(Request $request, TicketSettings $ticket_settings) 'ticketcategory' => 'required', 'priority' => 'required', 'message' => 'required', - 'g-recaptcha-response' => ['required', 'recaptcha'], + 'g-recaptcha-response' => [$generalSettings->recaptcha_enabled ? 'required' : null, 'recaptcha'], ] ); $ticket = new Ticket( From 130a3b308b80fb06548ac2c65464e3a4ff0248b6 Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Wed, 22 May 2024 14:26:18 +0200 Subject: [PATCH 312/514] [Feature] Addon implemented --- app/Http/Controllers/ProductController.php | 31 +++++++--- app/Http/Controllers/ServerController.php | 59 +++++++++++++----- lang/en.json | 2 +- routes/web.php | 2 +- themes/default/views/servers/create.blade.php | 61 ++++++++----------- 5 files changed, 95 insertions(+), 60 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 5f3d94dc1..ae9c1548c 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -14,7 +14,7 @@ use Illuminate\Support\Collection; class ProductController extends Controller -{ +{ private $pterodactyl; public function __construct(PterodactylSettings $ptero_settings) @@ -97,30 +97,41 @@ public function getLocationsBasedOnEgg(Request $request, Egg $egg) } /** - * @param Node $node + * @param Int $location * @param Egg $egg * @return Collection|JsonResponse */ - public function getProductsBasedOnNode(Egg $egg, Node $node) + public function getProductsBasedOnLocation(Egg $egg, Int $location) { - if (is_null($egg->id) || is_null($node->id)) { - return response()->json('node and egg id is required', '400'); + if (is_null($egg->id) || is_null($location)) { + return response()->json('location and egg id is required', '400'); } + // Get all nodes in this location + $nodes = Node::query() + ->where('location_id', '=', $location) + ->get(); + $products = Product::query() ->where('disabled', '=', false) - ->whereHas('nodes', function (Builder $builder) use ($node) { - $builder->where('id', '=', $node->id); + ->whereHas('nodes', function (Builder $builder) use ($nodes) { + $builder->whereIn('id', $nodes->map(function ($node) { + return $node->id; + })); }) ->whereHas('eggs', function (Builder $builder) use ($egg) { $builder->where('id', '=', $egg->id); }) ->get(); - $pteroNode = $this->pterodactyl->getNode($node->id); + // Instead of the old node check, we will check if the product fits in any given node in the location foreach ($products as $key => $product) { - if ($product->memory > ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['memory'] || $product->disk > ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['disk']) { - $product->doesNotFit = true; + $product->doesNotFit = false; + foreach ($nodes as $node) { + $pteroNode = $this->pterodactyl->getNode($node->id); + if ($product->memory > ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['memory'] || $product->disk > ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['disk']) { + $product->doesNotFit = true; + } } } diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 0c18a5084..a6cd1b9a8 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -141,13 +141,10 @@ private function validateConfigurationRules(UserSettings $user_settings, ServerS $product = Product::findOrFail(FacadesRequest::input('product')); // Get node resource allocation info - $node = $product->nodes()->findOrFail(FacadesRequest::input('node')); - $nodeName = $node->name; - - // Check if node has enough memory and disk space - $checkResponse = $this->pterodactyl->checkNodeResources($node, $product->memory, $product->disk); - if ($checkResponse == false) { - return redirect()->route('servers.index')->with('error', __("The node '" . $nodeName . "' doesn't have the required memory or disk left to allocate this product.")); + $location = FacadesRequest::input('location'); + $availableNode = $this->getAvailableNode($location, $product); + if (!$availableNode) { + return redirect()->route('servers.index')->with('error', __("The chosen location doesn't have the required memory or disk left to allocate this product.")); } // Min. Credits @@ -179,7 +176,7 @@ private function validateConfigurationRules(UserSettings $user_settings, ServerS /** Store a newly created resource in storage. */ public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $generalSettings) { - /** @var Node $node */ + /** @var Location $location */ /** @var Egg $egg */ /** @var Product $product */ $validate_configuration = $this->validateConfigurationRules($user_settings, $server_settings, $generalSettings); @@ -190,15 +187,23 @@ public function store(Request $request, UserSettings $user_settings, ServerSetti $request->validate([ 'name' => 'required|max:191', - 'node' => 'required|exists:nodes,id', + 'location' => 'required|exists:locations,id', 'egg' => 'required|exists:eggs,id', 'product' => 'required|exists:products,id', ]); - //get required resources + // Get the product and egg $product = Product::query()->findOrFail($request->input('product')); $egg = $product->eggs()->findOrFail($request->input('egg')); - $node = $product->nodes()->findOrFail($request->input('node')); + + // Get an available node + $location = $request->input('location'); + $availableNode = $this->getAvailableNode($location, $product); + $node = Node::query()->find($availableNode); + + if(!$node) { + return redirect()->route('servers.index')->with('error', __("No nodes satisfying the requirements for automatic deployment on this location were found.")); + } $server = $request->user()->servers()->create([ 'name' => $request->input('name'), @@ -316,7 +321,7 @@ public function show(Server $server, ServerSettings $server_settings, GeneralSet }) ->get(); - // Set the each product eggs array to just contain the eggs name + // Set each product eggs array to just contain the eggs name foreach ($products as $product) { $product->eggs = $product->eggs->pluck('name')->toArray(); if ($product->memory - $currentProduct->memory > ($pteroNode['memory'] * ($pteroNode['memory_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['memory'] || $product->disk - $currentProduct->disk > ($pteroNode['disk'] * ($pteroNode['disk_overallocate'] + 100) / 100) - $pteroNode['allocated_resources']['disk']) { @@ -356,8 +361,8 @@ public function upgrade(Server $server, Request $request) // Check if node has enough memory and disk space $requireMemory = $newProduct->memory - $oldProduct->memory; $requiredisk = $newProduct->disk - $oldProduct->disk; - $checkResponse = $this->pterodactyl->checkNodeResources($node, $requireMemory, $requiredisk); - if ($checkResponse == false) { + $nodeFree = $this->pterodactyl->checkNodeResources($node, $requireMemory, $requiredisk); + if (!$nodeFree) { return redirect()->route('servers.index')->with('error', __("The node '" . $nodeName . "' doesn't have the required memory or disk left to upgrade the server.")); } @@ -412,4 +417,30 @@ public function upgrade(Server $server, Request $request) return redirect()->route('servers.show', ['server' => $server->id])->with('error', __('Not Enough Balance for Upgrade')); } } + + /** + * @param string $location + * @param Product $product + * @return int | null Node ID + */ + private function getAvailableNode(string $location, Product $product) + { + $collection = Node::query()->where('location_id', $location)->get(); + + // loop through nodes and check if the node has enough resources + foreach ($collection as $node) { + // Check if the node has enough memory and disk space + $freeNode = $this->pterodactyl->checkNodeResources($node, $product->memory, $product->disk); + // Remove the node from the collection if it doesn't have enough resources + if (!$freeNode) { + $collection->forget($node['id']); + } + } + + if($collection->isEmpty()) { + return null; + } + + return $collection->first()['id']; + } } diff --git a/lang/en.json b/lang/en.json index 633a4ec2c..91da6df34 100644 --- a/lang/en.json +++ b/lang/en.json @@ -71,7 +71,7 @@ "Change Status": "Change Status", "Profile updated": "Profile updated", "Server limit reached!": "Server limit reached!", - "The node '\" . $nodeName . \"' doesn't have the required memory or disk left to allocate this product.": "The node '\" . $nodeName . \"' doesn't have the required memory or disk left to allocate this product.", + "The chosen location doesn't have the required memory or disk left to allocate this product.": "The chosen location doesn't have the required memory or disk left to allocate this product.", "You are required to verify your email address before you can create a server.": "You are required to verify your email address before you can create a server.", "You are required to link your discord account before you can create a server.": "You are required to link your discord account before you can create a server.", "Server created": "Server created", diff --git a/routes/web.php b/routes/web.php index c575ea981..444d9485b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -95,7 +95,7 @@ //routes made for server create page to fetch product info Route::get('/products/nodes/egg/{egg?}', [FrontProductController::class, 'getNodesBasedOnEgg'])->name('products.nodes.egg'); Route::get('/products/locations/egg/{egg?}', [FrontProductController::class, 'getLocationsBasedOnEgg'])->name('products.locations.egg'); - Route::get('/products/products/{egg?}/{node?}', [FrontProductController::class, 'getProductsBasedOnNode'])->name('products.products.node'); + Route::get('/products/products/{egg?}/{location?}', [FrontProductController::class, 'getProductsBasedOnLocation'])->name('products.products.location'); //payments Route::get('checkout/{shopProduct}', [PaymentController::class, 'checkOut'])->name('checkout'); diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 588da5a40..4dfadea55 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -133,32 +133,25 @@ class="custom-select"> </div> </div> - <div class="form-group"> - <label for="node">{{ __('Node') }}</label> - <select name="node" required id="node" x-model="selectedNode" - :disabled="!fetchedLocations" @change="fetchProducts();" class="custom-select"> - <option x-text="getNodeInputText()" disabled selected hidden value="null"> + <div class="form-group"> + <label for="location">{{ __('Location') }}</label> + <select name="location" required id="location" x-model="selectedLocation" :disabled="!fetchedLocations" + @change="fetchProducts();" class="custom-select"> + <option x-text="getLocationInputText()" disabled selected hidden value="null"> + </option> + + <template x-for="location in locations" :key="location.id"> + <option x-text="location.name" :value="location.id"> </option> - - <template x-for="location in locations" :key="location.id"> - <optgroup :label="location.name"> - - <template x-for="node in location.nodes" :key="node.id"> - <option x-text="node.name" :value="node.id"> - - </option> - </template> - </optgroup> - </template> - + </template> </select> - </div> + </div> </div> </div> </div> <div class="w-100"></div> - <div class="col" x-show="selectedNode != null"> + <div class="col" x-show="selectedLocation != null"> <div class="row mt-4 justify-content-center"> <template x-for="product in products" :key="product.id"> <div class="card col-xl-3 col-lg-3 col-md-4 col-sm-10 mr-2 ml-2 "> @@ -248,7 +241,7 @@ class="custom-select"> product.doesNotFit == true || submitClicked ? 'disabled' : ''" class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" - x-text="product.doesNotFit == true ? '{{ __('Server cant fit on this Node') }}' : (product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ $credits_display_name }}!' : '{{ __('Create server') }}')"> + x-text="product.doesNotFit == true ? '{{ __('Server cant fit on this Location') }}' : (product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ $credits_display_name }}!' : '{{ __('Create server') }}')"> </button> </div> @@ -278,13 +271,13 @@ function serverApp() { name: null, selectedNest: null, selectedEgg: null, - selectedNode: null, + selectedLocation: null, selectedProduct: null, //selected objects based on input selectedNestObject: {}, selectedEggObject: {}, - selectedNodeObject: {}, + selectedLocationObject: {}, selectedProductObject: {}, //values @@ -309,7 +302,7 @@ function serverApp() { this.locations = []; this.products = []; this.selectedEgg = 'null'; - this.selectedNode = 'null'; + this.selectedLocation = 'null'; this.selectedProduct = 'null'; this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest) @@ -343,7 +336,7 @@ function serverApp() { this.fetchedProducts = false; this.locations = []; this.products = []; - this.selectedNode = 'null'; + this.selectedLocation = 'null'; this.selectedProduct = 'null'; let response = await axios.get(`{{ route('products.locations.egg') }}/${this.selectedEgg}`) @@ -354,7 +347,7 @@ function serverApp() { //automatically select the first entry if there is only 1 if (this.locations.length === 1 && this.locations[0]?.nodes?.length === 1) { - this.selectedNode = this.locations[0]?.nodes[0]?.id; + this.selectedLocation = this.locations[0]?.id; await this.fetchProducts(); return; } @@ -366,7 +359,7 @@ function serverApp() { /** * @description fetch all available products based on the selected node * @note called whenever a node is selected - * @see selectedNode + * @see selectedLocation */ async fetchProducts() { this.loading = true; @@ -375,7 +368,7 @@ function serverApp() { this.selectedProduct = 'null'; let response = await axios.get( - `{{ route('products.products.node') }}/${this.selectedEgg}/${this.selectedNode}`) + `{{ route('products.products.location') }}/${this.selectedEgg}/${this.selectedLocation}`) .catch(console.error) this.fetchedProducts = true; @@ -410,10 +403,10 @@ function serverApp() { this.selectedNestObject = this.nests.find(nest => nest.id == this.selectedNest) ?? {} this.selectedEggObject = this.eggs.find(egg => egg.id == this.selectedEgg) ?? {} - this.selectedNodeObject = {}; + this.selectedLocationObject = {}; this.locations.forEach(location => { - if (!this.selectedNodeObject?.id) { - this.selectedNodeObject = location.nodes.find(node => node.id == this.selectedNode) ?? + if (!this.selectedLocationObject?.id) { + this.selectedLocationObject = location.nodes.find(node => node.id == this.selectedLocation) ?? {}; } }) @@ -429,17 +422,17 @@ function serverApp() { isFormValid() { if (Object.keys(this.selectedNestObject).length === 0) return false; if (Object.keys(this.selectedEggObject).length === 0) return false; - if (Object.keys(this.selectedNodeObject).length === 0) return false; + if (Object.keys(this.selectedLocationObject).length === 0) return false; if (Object.keys(this.selectedProductObject).length === 0) return false; return !!this.name; }, - getNodeInputText() { + getLocationInputText() { if (this.fetchedLocations) { if (this.locations.length > 0) { - return '{{ __('Please select a node ...') }}'; + return '{{ __('Please select a location ...') }}'; } - return '{{ __('No nodes found matching current configuration') }}' + return '{{ __('No location found matching current configuration') }}' } return '{{ __('---') }}'; }, From ca3d1b00a0acb52e7ba7f9f1e2cfb576f5c1ae00 Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Wed, 22 May 2024 15:02:28 +0200 Subject: [PATCH 313/514] [Feat] Language additions [Bug] ControlPanel leftovers changed to CtrlPanel --- .github/ISSUE_TEMPLATE/bug.yml | 6 +++--- .github/SECURITY.md | 4 ++-- app/Http/Controllers/ServerController.php | 2 +- app/Traits/Invoiceable.php | 2 +- docker/README.md | 6 +++--- docker/docker-compose.yml | 16 ++++++++-------- lang/bg.json | 2 +- lang/bs.json | 2 +- lang/cs.json | 2 +- lang/de.json | 2 +- lang/en.json | 4 ++-- lang/es.json | 2 +- lang/fr.json | 2 +- lang/he.json | 2 +- lang/hi.json | 2 +- lang/hu.json | 2 +- lang/it.json | 2 +- lang/nl.json | 2 +- lang/pl.json | 2 +- lang/pt.json | 2 +- lang/ro.json | 2 +- lang/ru.json | 2 +- lang/sh.json | 2 +- lang/sk.json | 2 +- lang/sr.json | 2 +- lang/sv.json | 2 +- lang/tr.json | 2 +- lang/zh.json | 2 +- package-lock.json | 2 +- phpunit.xml | 2 +- public/install/forms.php | 2 +- public/install/functions.php | 2 +- public/install/index.php | 4 ++-- themes/BlueInfinity/views/layouts/app.blade.php | 2 +- themes/BlueInfinity/views/layouts/main.blade.php | 4 ++-- .../default/views/admin/settings/index.blade.php | 6 +++--- themes/default/views/auth/login.blade.php | 2 +- themes/default/views/layouts/app.blade.php | 2 +- themes/default/views/layouts/main.blade.php | 4 ++-- 39 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index f9e8e9ec3..a5e0e3666 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -31,10 +31,10 @@ body: validations: required: false - type: textarea - id: controlpanel-logs + id: ctrlpanel-logs attributes: - label: Controlpanel Logs - description: Please copy and paste your laravel-log output. You may also provide a link to it using the following command `tail -n 100 /var/www/controlpanel/storage/logs/laravel.log | nc pteropaste.com 99` + label: CtrlPanel Logs + description: Please copy and paste your laravel-log output. You may also provide a link to it using the following command `tail -n 100 /var/www/ctrlpanel/storage/logs/laravel.log | nc pteropaste.com 99` render: Shell - type: textarea id: additional-info diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 278e81e71..05a6da9bf 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -12,6 +12,6 @@ At this time, we only accept vulnerability reports through GitHub Advisories. We ## Supported Versions -### ControlPanel Versions +### CtrlPanel Versions -We strongly recommend using or upgrading to the latest version of ControlPanel to ensure you have access to the latest security fixes and enhancements. +We strongly recommend using or upgrading to the latest version of CtrlPanel to ensure you have access to the latest security fixes and enhancements. diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index a6cd1b9a8..935097bcc 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -398,7 +398,7 @@ public function upgrade(Server $server, Request $request) // Remove the allocation property from the server object as it is not a column in the database unset($server->allocation); - // Update the server on controlpanel + // Update the server on CtrlPanel $server->update([ 'product_id' => $newProduct->id, 'updated_at' => now(), diff --git a/app/Traits/Invoiceable.php b/app/Traits/Invoiceable.php index 182333a58..ef212bd0e 100644 --- a/app/Traits/Invoiceable.php +++ b/app/Traits/Invoiceable.php @@ -54,7 +54,7 @@ public function createInvoice(Payment $payment, ShopProduct $shopProduct, Invoic $invoice = DailyInvoice::make() - ->template('controlpanel') + ->template('CtrlPanel') ->name(__("Invoice")) ->buyer($customer) ->seller($seller) diff --git a/docker/README.md b/docker/README.md index 1581bd17a..d76483b7c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -3,9 +3,9 @@ cd into the project directory and run the following command: `sh bin/startdocker.sh` This should start building the images and start the containers. -After that you need to go into the controlpanel_php container and run some commands: +After that you need to go into the ctrlpanel_php container and run some commands: -Type `docker exec -it controlpanel_php ash` to go into the container and run the following commands: +Type `docker exec -it ctrlpanel_php ash` to go into the container and run the following commands: ```shell composer install @@ -17,7 +17,7 @@ php artisan migrate --seed --force ## Setting up testing environment -Create the .env.testing file to your needs. Then once done you need to go into your phpmyadmin to create a new database named __controlpanel_test__. +Create the .env.testing file to your needs. Then once done you need to go into your phpmyadmin to create a new database named __ctrlpanel_test__. Visit http://127.0.0.1:8080/ and create your database. Now you're ready to run the following commands which switches to the testing config, migrates the test database and seeds it. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 56c9e24de..d41e258bc 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -8,7 +8,7 @@ services: build: context: ../ dockerfile: docker/nginx/Dockerfile - container_name: controlpanel_nginx + container_name: ctrlpanel_nginx ports: - 80:80 volumes: @@ -21,14 +21,14 @@ services: mysql: image: mysql - container_name: controlpanel_mysql + container_name: ctrlpanel_mysql restart: unless-stopped tty: true ports: - "3306:3306" environment: - MYSQL_DATABASE: controlpanel - MYSQL_USER: controlpanel + MYSQL_DATABASE: ctrlpanel + MYSQL_USER: ctrlpanel MYSQL_PASSWORD: root MYSQL_ROOT_PASSWORD: root volumes: @@ -40,7 +40,7 @@ services: build: context: ../ dockerfile: docker/php/Dockerfile - container_name: controlpanel_php + container_name: ctrlpanel_php volumes: - "../:/var/www/html" networks: @@ -48,13 +48,13 @@ services: phpmyadmin: image: phpmyadmin/phpmyadmin - container_name: controlpanel_phpmyadmin + container_name: ctrlpanel_phpmyadmin depends_on: - mysql ports: - '8080:80' environment: - - PMA_HOST=controlpanel_mysql + - PMA_HOST=ctrlpanel_mysql - PMA_USER=root - PMA_PASSWORD=root - PMA_ARBITRARY=1 @@ -62,4 +62,4 @@ services: - laravel volumes: - mysql: \ No newline at end of file + mysql: diff --git a/lang/bg.json b/lang/bg.json index a5c070c57..1618b1ccd 100644 --- a/lang/bg.json +++ b/lang/bg.json @@ -126,7 +126,7 @@ "Support server": "Сървър за поддръжка", "Documentation": "Документация", "Github": "GitHub", - "Support ControlPanel": "Подкрепете Control Panel", + "Support CtrlPanel": "Подкрепете CtrlPanel", "Servers": "Сървъри", "Total": "Общо", "Payments": "Плащания", diff --git a/lang/bs.json b/lang/bs.json index fe4f110bb..79615b407 100644 --- a/lang/bs.json +++ b/lang/bs.json @@ -112,7 +112,7 @@ "Support server": "Support server", "Documentation": "Documentation", "Github": "Github", - "Support ControlPanel": "Support ControlPanel", + "Support CtrlPanel": "Support CtrlPanel", "Servers": "Servers", "Total": "Total", "Payments": "Payments", diff --git a/lang/cs.json b/lang/cs.json index a0074d402..146670294 100644 --- a/lang/cs.json +++ b/lang/cs.json @@ -126,7 +126,7 @@ "Support server": "Server podpory", "Documentation": "Dokumentace", "Github": "GitHub", - "Support ControlPanel": "Podpořit ControlPanel", + "Support CtrlPanel": "Podpořit CtrlPanel", "Servers": "Servery", "Total": "Celkem", "Payments": "Platby", diff --git a/lang/de.json b/lang/de.json index e9ab272d9..2460899a3 100644 --- a/lang/de.json +++ b/lang/de.json @@ -126,7 +126,7 @@ "Support server": "Discord Server", "Documentation": "Dokumentation", "Github": "Github", - "Support ControlPanel": "Unterstütze CtrlPanel.gg", + "Support CtrlPanel": "Unterstütze CtrlPanel.gg", "Servers": "Server", "Total": "Gesamt", "Payments": "Zahlungen", diff --git a/lang/en.json b/lang/en.json index 91da6df34..da51ad810 100644 --- a/lang/en.json +++ b/lang/en.json @@ -145,7 +145,7 @@ "Support server": "Support server", "Documentation": "Documentation", "Github": "Github", - "Support ControlPanel": "Support ControlPanel", + "Support CtrlPanel": "Support CtrlPanel", "Servers": "Servers", "Total": "Total", "Payments": "Payments", @@ -166,7 +166,7 @@ "You reached the Pterodactyl perPage limit. Please make sure to set it higher than your server count.": "You reached the Pterodactyl perPage limit. Please make sure to set it higher than your server count.", "You can do that in settings.": "You can do that in settings.", "Note": "Note", - "If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on ControlPanel. Try clicking the button below.": "If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on ControlPanel. Try clicking the button below.", + "If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on CtrlPanel. Try clicking the button below.": "If this error persists even after changing the limit, it might mean a server was deleted on Pterodactyl, but not on CtrlPanel. Try clicking the button below.", "Sync servers": "Sync servers", "Node": "Node", "Server count": "Server count", diff --git a/lang/es.json b/lang/es.json index 50ecfb4a4..f4c3121a0 100644 --- a/lang/es.json +++ b/lang/es.json @@ -126,7 +126,7 @@ "Support server": "Servidor de Ayuda", "Documentation": "Documentación", "Github": "GitHub", - "Support ControlPanel": "Apoya ControlPanel", + "Support CtrlPanel": "Apoya CtrlPanel", "Servers": "Servidores", "Total": "Total", "Payments": "Pagos", diff --git a/lang/fr.json b/lang/fr.json index 16e7150f3..8811187d7 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -126,7 +126,7 @@ "Support server": "Serveur de support", "Documentation": "Documentation", "Github": "Github", - "Support ControlPanel": "ControlPanel Support", + "Support CtrlPanel": "CtrlPanel Support", "Servers": "Serveurs", "Total": "Total", "Payments": "Paiments", diff --git a/lang/he.json b/lang/he.json index 4d09b0397..04af79956 100644 --- a/lang/he.json +++ b/lang/he.json @@ -126,7 +126,7 @@ "Support server": "שרת תמיכה", "Documentation": "מדריך", "Github": "Github/גיטאהב", - "Support ControlPanel": "תמיכת ControlPanel", + "Support CtrlPanel": "תמיכת CtrlPanel", "Servers": "שרתים", "Total": "בסך הכל", "Payments": "תשלומים", diff --git a/lang/hi.json b/lang/hi.json index c36b7d014..5b310de06 100644 --- a/lang/hi.json +++ b/lang/hi.json @@ -126,7 +126,7 @@ "Support server": "समर्थन सर्वर", "Documentation": "प्रलेखन", "Github": "गिटहब", - "Support ControlPanel": "समर्थन नियंत्रण पैनल", + "Support CtrlPanel": "CtrlPanel का समर्थन करें", "Servers": "सर्वरस", "Total": "कुल", "Payments": "भुगतान", diff --git a/lang/hu.json b/lang/hu.json index af3ac9622..2479fdb5b 100644 --- a/lang/hu.json +++ b/lang/hu.json @@ -126,7 +126,7 @@ "Support server": "Szerver támogatása", "Documentation": "Dokumentáció", "Github": "Github", - "Support ControlPanel": "ControlPanel támogatása", + "Support CtrlPanel": "CtrlPanel támogatása", "Servers": "Szerverek", "Total": "Összesen", "Payments": "Fizetések", diff --git a/lang/it.json b/lang/it.json index 1fce8de29..12f1c29f1 100644 --- a/lang/it.json +++ b/lang/it.json @@ -126,7 +126,7 @@ "Support server": "Server di supporto", "Documentation": "Documentazione", "Github": "GitHub", - "Support ControlPanel": "Supporta ControlPanel", + "Support CtrlPanel": "Supporta CtrlPanel", "Servers": "Servers", "Total": "Totale", "Payments": "Pagamenti", diff --git a/lang/nl.json b/lang/nl.json index 6db2f669d..8b1add000 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -126,7 +126,7 @@ "Support server": "Ondersteuningsserver", "Documentation": "Documentatie", "Github": "Github", - "Support ControlPanel": "Ondersteuning ControlPanel", + "Support CtrlPanel": "Ondersteuning CtrlPanel", "Servers": "Servers", "Total": "Totaal", "Payments": "Betalingen", diff --git a/lang/pl.json b/lang/pl.json index d14b00803..05e68bac1 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -126,7 +126,7 @@ "Support server": "Serwer pomocy", "Documentation": "Dokumentacja", "Github": "Github", - "Support ControlPanel": "Wesprzyj ControlPanel", + "Support CtrlPanel": "Wesprzyj CtrlPanel", "Servers": "Serwery", "Total": "Razem", "Payments": "Płatności", diff --git a/lang/pt.json b/lang/pt.json index 9083b97d4..e07ec430b 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -126,7 +126,7 @@ "Support server": "Servidor de suporte", "Documentation": "Documentação", "Github": "Github", - "Support ControlPanel": "Suporte para ControlPanel", + "Support CtrlPanel": "Suporte para CtrlPanel", "Servers": "Servidores", "Total": "Total", "Payments": "Pagamentos", diff --git a/lang/ro.json b/lang/ro.json index d9a949671..9aeb0f05b 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -126,7 +126,7 @@ "Support server": "Support server", "Documentation": "Documentation", "Github": "Github", - "Support ControlPanel": "Support ControlPanel", + "Support CtrlPanel": "Support CtrlPanel", "Servers": "Servers", "Total": "Total", "Payments": "Payments", diff --git a/lang/ru.json b/lang/ru.json index ec254e371..a9bfeb9f5 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -126,7 +126,7 @@ "Support server": "Сервер поддержки", "Documentation": "Документация", "Github": "GitHub", - "Support ControlPanel": "Поддержка панели управления", + "Support CtrlPanel": "Поддержка CtrlPanel", "Servers": "Серверы", "Total": "Всего", "Payments": "Оплаты", diff --git a/lang/sh.json b/lang/sh.json index c65182bb1..1809b0c01 100644 --- a/lang/sh.json +++ b/lang/sh.json @@ -112,7 +112,7 @@ "Support server": "Support server", "Documentation": "Documentation", "Github": "Github", - "Support ControlPanel": "Support ControlPanel", + "Support CtrlPanel": "Support CtrlPanel", "Servers": "Servers", "Total": "Total", "Payments": "Payments", diff --git a/lang/sk.json b/lang/sk.json index 2db59f349..99fa908e3 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -126,7 +126,7 @@ "Support server": "Server podpory", "Documentation": "Dokumentácia", "Github": "Github", - "Support ControlPanel": "Podporiť ControlPanel", + "Support CtrlPanel": "Podporiť CtrlPanel", "Servers": "Servery", "Total": "Celkom", "Payments": "Platby", diff --git a/lang/sr.json b/lang/sr.json index 50cab9924..65ecd37cc 100644 --- a/lang/sr.json +++ b/lang/sr.json @@ -126,7 +126,7 @@ "Support server": "Server za podršku", "Documentation": "Dokumentacija", "Github": "GitHub", - "Support ControlPanel": "Podrži ControlPanel", + "Support CtrlPanel": "Podrži CtrlPanel", "Servers": "Serveri", "Total": "Ukupno", "Payments": "Plaćanja", diff --git a/lang/sv.json b/lang/sv.json index 552f0def3..d26a47107 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -126,7 +126,7 @@ "Support server": "Stödservern", "Documentation": "Dokumentation", "Github": "Github", - "Support ControlPanel": "Support ControlPanel", + "Support CtrlPanel": "Support CtrlPanel", "Servers": "Servers", "Total": "Total", "Payments": "Payments", diff --git a/lang/tr.json b/lang/tr.json index f05ebe60e..1a31d925a 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -126,7 +126,7 @@ "Support server": "Destek sunucusu", "Documentation": "Dökümantasyon", "Github": "GitHub", - "Support ControlPanel": "ContrılPanel'i destekle", + "Support CtrlPanel": "CtrlPanel'i destekle", "Servers": "Sunucular", "Total": "Toplam", "Payments": "Ödemeler", diff --git a/lang/zh.json b/lang/zh.json index 58e00e356..c18dad757 100644 --- a/lang/zh.json +++ b/lang/zh.json @@ -126,7 +126,7 @@ "Support server": "支持服务器", "Documentation": "文档", "Github": "Github", - "Support ControlPanel": "支持我们", + "Support CtrlPanel": "支持我们", "Servers": "服务器", "Total": "总数", "Payments": "支付费用", diff --git a/package-lock.json b/package-lock.json index 89d691a53..95a8ff2b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "controlpanel", + "name": "CtrlPanel", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/phpunit.xml b/phpunit.xml index fc15f97bc..1c2c01844 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -22,7 +22,7 @@ <env name="BCRYPT_ROUNDS" value="4"/> <env name="CACHE_DRIVER" value="array"/> <env name="DB_CONNECTION" value="mysql"/> - <env name="DB_DATABASE" value="controlpanel_test"/> + <env name="DB_DATABASE" value="ctrlpanel_test"/> <env name="MAIL_MAILER" value="array"/> <env name="QUEUE_CONNECTION" value="sync"/> <env name="SESSION_DRIVER" value="array"/> diff --git a/public/install/forms.php b/public/install/forms.php index 96f1dfa17..3ca18c010 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -87,7 +87,7 @@ header('LOCATION: index.php?step=3'); } catch (\Throwable $th) { wh_log('Feeding the Database failed', 'error'); - header("LOCATION: index.php?step=2.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + header("LOCATION: index.php?step=2.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/ctrlpanel/storage/logs !"); } } diff --git a/public/install/functions.php b/public/install/functions.php index a5f2b0454..5e8b847aa 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -262,7 +262,7 @@ function wh_log(string $message, string $level = 'info', array $context = []): v $stream = new StreamHandler(dirname(__FILE__, 3) . '/storage/logs/installer.log', Logger::DEBUG); $stream->setFormatter($formatter); - $log = new Logger('ControlPanel'); + $log = new Logger('CtrlPanel'); $log->pushHandler($stream); switch (strtolower($level)) { diff --git a/public/install/index.php b/public/install/index.php index 6afd2e70c..ee36cc4dd 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -143,7 +143,7 @@ function cardStart($title, $subtitle = null) <div class="form-group"> <div class="flex flex-col mb-3"> <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="controlpaneluser" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="ctrlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> <div class="form-group"> @@ -156,7 +156,7 @@ function cardStart($title, $subtitle = null) <div class="form-group"> <div class="flex flex-col"> <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="controlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> diff --git a/themes/BlueInfinity/views/layouts/app.blade.php b/themes/BlueInfinity/views/layouts/app.blade.php index 376f1d920..e8c5650d8 100644 --- a/themes/BlueInfinity/views/layouts/app.blade.php +++ b/themes/BlueInfinity/views/layouts/app.blade.php @@ -6,7 +6,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <meta content="{{ config('SETTINGS::SYSTEM:SEO_TITLE') }}" property="og:title"> <meta content="{{ config('SETTINGS::SYSTEM:SEO_DESCRIPTION') }}" property="og:description"> - <meta content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}' property="og:image"> + <meta content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/ctrlpanel_logo.png') }}' property="og:image"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> diff --git a/themes/BlueInfinity/views/layouts/main.blade.php b/themes/BlueInfinity/views/layouts/main.blade.php index b6549a7a0..785b42780 100644 --- a/themes/BlueInfinity/views/layouts/main.blade.php +++ b/themes/BlueInfinity/views/layouts/main.blade.php @@ -11,7 +11,7 @@ <meta content="{{ $website_settings->seo_title }}" property="og:title"> <meta content="{{ $website_settings->seo_description }}" property="og:description"> <meta - content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}' + content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/ctrlpanel_logo.png') }}' property="og:image"> <title>{{ config('app.name', 'Laravel') }}</title> <link rel="icon" @@ -201,7 +201,7 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> <!-- Brand Logo --> <a href="{{ route('home') }}" class="brand-link"> <img width="64" height="64" - src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" + src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/ctrlpanel_logo.png') }}" alt="{{ config('app.name', 'Laravel') }} Logo" class="brand-image img-circle" style="opacity: .8"> <span class="brand-text font-weight-light">{{ config('app.name', 'CtrlPanel.gg') }}</span> diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 799e426de..4d07a9e31 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -120,7 +120,7 @@ class="nav-icon fas {{ $options['category_icon'] ?? 'fas fa-cog' }}"></i> <div class="row"> <div class="ml-5 card" style="width: 18rem;"> <span class="text-center h3">{{ __('FavIcon') }} </span> - <img src="{{ Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('images/controlpanel_logo.png') }}" + <img src="{{ Storage::disk('public')->exists('favicon.ico') ? asset('storage/favicon.ico') : asset('images/ctrlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..."> <div class="card-body"> @@ -132,7 +132,7 @@ class="card-img-top" alt="..."> <div class="ml-5 card" style="width: 18rem;"> <span class="text-center h3">{{ __('Icon') }} </span> - <img src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" + <img src="{{ Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/ctrlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..."> <div class="card-body"> @@ -144,7 +144,7 @@ class="form-control" name="icon" id="icon"> <div class="ml-5 card" style="width: 18rem;"> <span class="text-center h3">{{ __('Login-page Logo') }} </span> - <img src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" + <img src="{{ Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/ctrlpanel_logo.png') }}" style="width:5vw;display: block; margin-left: auto;margin-right: auto" class="card-img-top" alt="..."> <div class="card-body"> diff --git a/themes/default/views/auth/login.blade.php b/themes/default/views/auth/login.blade.php index c570550d9..fe743dd35 100644 --- a/themes/default/views/auth/login.blade.php +++ b/themes/default/views/auth/login.blade.php @@ -10,7 +10,7 @@ <a href="{{ route('welcome') }}" class="h1 mb-2"><b class="mr-1">{{ config('app.name', 'Laravel') }}</b></a> @if ($website_settings->enable_login_logo) - <img src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}" + <img src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/ctrlpanel_logo.png') }}" alt="{{ config('app.name', 'CtrlPanel.gg') }} Logo" style="opacity: .8; max-width:100%; height: 150px; margin-top: 10px;"> @endif </div> diff --git a/themes/default/views/layouts/app.blade.php b/themes/default/views/layouts/app.blade.php index 240eb2466..3f6b504f5 100644 --- a/themes/default/views/layouts/app.blade.php +++ b/themes/default/views/layouts/app.blade.php @@ -7,7 +7,7 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <meta content="{{ $website_settings->seo_title }}" property="og:title"> <meta content="{{ $website_settings->seo_description }}" property="og:description"> - <meta content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('/logo.png') : asset('images/controlpanel_logo.png') }}' property="og:image"> + <meta content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('/logo.png') : asset('images/ctrlpanel_logo.png') }}' property="og:image"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> diff --git a/themes/default/views/layouts/main.blade.php b/themes/default/views/layouts/main.blade.php index 9631ef64d..66c79d6e7 100644 --- a/themes/default/views/layouts/main.blade.php +++ b/themes/default/views/layouts/main.blade.php @@ -11,7 +11,7 @@ <meta content="{{ $website_settings->seo_title }}" property="og:title"> <meta content="{{ $website_settings->seo_description }}" property="og:description"> <meta - content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/controlpanel_logo.png') }}' + content='{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('logo.png') ? asset('storage/logo.png') : asset('images/ctrlpanel_logo.png') }}' property="og:image"> <title>{{ config('app.name', 'Laravel') }}</title> <link rel="icon" @@ -201,7 +201,7 @@ class="dropdown-item dropdown-footer">{{ __('Mark all as read') }}</a> <!-- Brand Logo --> <a href="{{ route('home') }}" class="brand-link"> <img width="64" height="64" - src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/controlpanel_logo.png') }}" + src="{{ \Illuminate\Support\Facades\Storage::disk('public')->exists('icon.png') ? asset('storage/icon.png') : asset('images/ctrlpanel_logo.png') }}" alt="{{ config('app.name', 'Laravel') }} Logo" class="brand-image img-circle" style="opacity: .8"> <span class="brand-text font-weight-light">{{ config('app.name', 'CtrlPanel.gg') }}</span> From 920279cc37954b457b945613a805d7d5fca23aae Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Wed, 22 May 2024 13:18:21 +0000 Subject: [PATCH 314/514] Buy more button, if user does not have enough credits --- app/Http/Controllers/ServerController.php | 3 ++- lang/en.json | 3 ++- themes/default/views/servers/create.blade.php | 9 +++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 0c18a5084..c52dc9316 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -122,7 +122,8 @@ public function create(UserSettings $user_settings, ServerSettings $server_setti 'user' => Auth::user(), 'server_creation_enabled' => $server_settings->creation_enabled, 'min_credits_to_make_server' => $user_settings->min_credits_to_make_server, - 'credits_display_name' => $general_settings->credits_display_name + 'credits_display_name' => $general_settings->credits_display_name, + 'store_enabled' => $general_settings->store_enabled ]); } diff --git a/lang/en.json b/lang/en.json index 633a4ec2c..25f16da8b 100644 --- a/lang/en.json +++ b/lang/en.json @@ -655,5 +655,6 @@ "You can not see your Referral Code": "You can not see your Referral Code", "SERVER NAME": "SERVER NAME", "STORAGE": "STORAGE", - "Cancel": "Cancel" + "Cancel": "Cancel", + "Buy more": "Buy more" } diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 588da5a40..417dae942 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -250,6 +250,15 @@ class="custom-select"> class="btn btn-primary btn-block mt-2" @click="setProduct(product.id);" x-text="product.doesNotFit == true ? '{{ __('Server cant fit on this Node') }}' : (product.minimum_credits > user.credits || product.price > user.credits ? '{{ __('Not enough') }} {{ $credits_display_name }}!' : '{{ __('Create server') }}')"> </button> + @if (env('APP_ENV') == 'local' || $store_enabled) + <template x-if="product.price > user.credits"> + <a href="{{ route('store.index') }}"> + <button type="button" class="btn btn-warning btn-block mt-2"> + {{ __('Buy more') }} {{ $credits_display_name }} + </button> + </a> + </template> + @endif </div> </div> From f92552f7fdc7e08d7eb74cfbc8a3c1682c212e75 Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Wed, 22 May 2024 15:30:28 +0200 Subject: [PATCH 315/514] [Feat] Remove unused image [Bug] ControlPanel leftovers changed to CtrlPanel --- app/Traits/Invoiceable.php | 2 +- public/images/controlpanel.png | Bin 143251 -> 0 bytes ...ontrolpanel_logo.png => ctrlpanel_logo.png} | Bin ...trolpanel.blade.php => ctrlpanel.blade.php} | 0 4 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 public/images/controlpanel.png rename public/images/{controlpanel_logo.png => ctrlpanel_logo.png} (100%) rename themes/default/views/vendor/invoices/templates/{controlpanel.blade.php => ctrlpanel.blade.php} (100%) diff --git a/app/Traits/Invoiceable.php b/app/Traits/Invoiceable.php index ef212bd0e..3ee77c90e 100644 --- a/app/Traits/Invoiceable.php +++ b/app/Traits/Invoiceable.php @@ -54,7 +54,7 @@ public function createInvoice(Payment $payment, ShopProduct $shopProduct, Invoic $invoice = DailyInvoice::make() - ->template('CtrlPanel') + ->template('ctrlpanel') ->name(__("Invoice")) ->buyer($customer) ->seller($seller) diff --git a/public/images/controlpanel.png b/public/images/controlpanel.png deleted file mode 100644 index 63a4d3bf288f418e5377f7c6454fba2d88972a6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 143251 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:^P~;r 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 z�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 S_-r!+5O})!xvX<aXaWGNAuNsn diff --git a/public/images/controlpanel_logo.png b/public/images/ctrlpanel_logo.png similarity index 100% rename from public/images/controlpanel_logo.png rename to public/images/ctrlpanel_logo.png diff --git a/themes/default/views/vendor/invoices/templates/controlpanel.blade.php b/themes/default/views/vendor/invoices/templates/ctrlpanel.blade.php similarity index 100% rename from themes/default/views/vendor/invoices/templates/controlpanel.blade.php rename to themes/default/views/vendor/invoices/templates/ctrlpanel.blade.php From da622b82c36175ebb8b4c96baaa5868418e72c72 Mon Sep 17 00:00:00 2001 From: kenshin133 <bufmouse13@gmail.com> Date: Wed, 22 May 2024 10:36:32 -0400 Subject: [PATCH 316/514] fixed product description showing up instead of name in two locations This seems to be referenced two places, admin/servers and admin/users/1 I could not find any other effects of this change but do feel free to double check my work. themes/default/views/admin/servers/table.blade.php And themes/default/views/admin/users/show.blade.php and perhaps themes/default/views/admin/users/edit.blade.php have data: 'resources', name: 'product.name', In them, another fix would be to alter this to data: 'product.name' but the proposed change seems like the right one. --- app/Http/Controllers/Admin/ServerController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php index 97ee15535..b2775bb1c 100644 --- a/app/Http/Controllers/Admin/ServerController.php +++ b/app/Http/Controllers/Admin/ServerController.php @@ -228,7 +228,7 @@ public function dataTable(Request $request) return '<a href="' . route('admin.users.show', $server->user->id) . '">' . $server->user->name . '</a>'; }) ->addColumn('resources', function (Server $server) { - return $server->product->description; + return $server->product->name; }) ->addColumn('actions', function (Server $server) { $suspendColor = $server->isSuspended() ? 'btn-success' : 'btn-warning'; From a2da2d12e3edbde3876579ee7c5a44c7d2443ad7 Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Wed, 22 May 2024 21:27:06 +0200 Subject: [PATCH 317/514] [Feature]: Location description label on server create (optional setting) [Fix]: Node Description grabs name?? --- app/Http/Controllers/ServerController.php | 6 ++++-- app/Models/Pterodactyl/Location.php | 2 +- app/Settings/ServerSettings.php | 7 +++++++ themes/default/views/servers/create.blade.php | 14 +++++++++++--- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 935097bcc..4d7553f52 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -122,7 +122,8 @@ public function create(UserSettings $user_settings, ServerSettings $server_setti 'user' => Auth::user(), 'server_creation_enabled' => $server_settings->creation_enabled, 'min_credits_to_make_server' => $user_settings->min_credits_to_make_server, - 'credits_display_name' => $general_settings->credits_display_name + 'credits_display_name' => $general_settings->credits_display_name, + 'location_description_enabled' => $server_settings->location_description_enabled, ]); } @@ -333,7 +334,8 @@ public function show(Server $server, ServerSettings $server_settings, GeneralSet 'server' => $server, 'products' => $products, 'server_enable_upgrade' => $server_settings->enable_upgrade, - 'credits_display_name' => $general_settings->credits_display_name + 'credits_display_name' => $general_settings->credits_display_name, + 'location_description_enabled' => $server_settings->location_description_enabled, ]); } diff --git a/app/Models/Pterodactyl/Location.php b/app/Models/Pterodactyl/Location.php index 8d4bf7c10..ddb1f7155 100644 --- a/app/Models/Pterodactyl/Location.php +++ b/app/Models/Pterodactyl/Location.php @@ -53,7 +53,7 @@ public static function syncLocations() ], [ 'name' => $location['name'], - 'description' => $location['name'], + 'description' => $location['description'], ] ); } diff --git a/app/Settings/ServerSettings.php b/app/Settings/ServerSettings.php index 3a82ac3bc..8ed4547d1 100644 --- a/app/Settings/ServerSettings.php +++ b/app/Settings/ServerSettings.php @@ -9,6 +9,7 @@ class ServerSettings extends Settings public int $allocation_limit; public bool $creation_enabled; public bool $enable_upgrade; + public bool $location_description_enabled; public static function group(): string { @@ -25,6 +26,7 @@ public static function getValidations() 'allocation_limit' => 'required|integer|min:0', 'creation_enabled' => 'nullable|string', 'enable_upgrade' => 'nullable|string', + 'location_description_enabled' => 'nullable|string', ]; } @@ -52,6 +54,11 @@ public static function getOptionInputData() 'type' => 'boolean', 'description' => 'Enable the server upgrade feature.', ], + 'location_description_enabled' => [ + 'label' => 'Enable Location Description', + 'type' => 'boolean', + 'description' => 'Enable the location description field on the server creation page.', + ], ]; } } diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index 4dfadea55..b614ab180 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -135,11 +135,15 @@ class="custom-select"> <div class="form-group"> <label for="location">{{ __('Location') }}</label> + @if($location_description_enabled) + <i x-show="locationDescription != null" data-toggle="popover" data-trigger="click" + x-bind:data-content="locationDescription" + class="fas fa-info-circle"></i> + @endif <select name="location" required id="location" x-model="selectedLocation" :disabled="!fetchedLocations" @change="fetchProducts();" class="custom-select"> <option x-text="getLocationInputText()" disabled selected hidden value="null"> </option> - <template x-for="location in locations" :key="location.id"> <option x-text="location.name" :value="location.id"> </option> @@ -273,6 +277,7 @@ function serverApp() { selectedEgg: null, selectedLocation: null, selectedProduct: null, + locationDescription: null, //selected objects based on input selectedNestObject: {}, @@ -304,6 +309,7 @@ function serverApp() { this.selectedEgg = 'null'; this.selectedLocation = 'null'; this.selectedProduct = 'null'; + this.locationDescription = 'null'; this.eggs = this.eggsSave.filter(egg => egg.nest_id == this.selectedNest) @@ -338,6 +344,7 @@ function serverApp() { this.products = []; this.selectedLocation = 'null'; this.selectedProduct = 'null'; + this.locationDescription = null; let response = await axios.get(`{{ route('products.locations.egg') }}/${this.selectedEgg}`) .catch(console.error) @@ -348,6 +355,7 @@ function serverApp() { //automatically select the first entry if there is only 1 if (this.locations.length === 1 && this.locations[0]?.nodes?.length === 1) { this.selectedLocation = this.locations[0]?.id; + await this.fetchProducts(); return; } @@ -365,7 +373,7 @@ function serverApp() { this.loading = true; this.fetchedProducts = false; this.products = []; - this.selectedProduct = 'null'; + this.selectedProduct = null; let response = await axios.get( `{{ route('products.products.location') }}/${this.selectedEgg}/${this.selectedLocation}`) @@ -389,7 +397,7 @@ function serverApp() { } }) - + this.locationDescription = this.locations.find(location => location.id == this.selectedLocation).description ?? null; this.loading = false; this.updateSelectedObjects() }, From 66b1056d110b4bd946af0d47b92a5bc9f1f6ebc8 Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Wed, 22 May 2024 23:12:08 +0200 Subject: [PATCH 318/514] [Feature]: Additional error message when nodes are full --- themes/default/views/servers/create.blade.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/themes/default/views/servers/create.blade.php b/themes/default/views/servers/create.blade.php index b614ab180..56c775e97 100644 --- a/themes/default/views/servers/create.blade.php +++ b/themes/default/views/servers/create.blade.php @@ -150,6 +150,9 @@ class="fas fa-info-circle"></i> </template> </select> </div> + <div class="alert alert-danger p-2 m-2" x-show="selectedProduct != null && locations.length == 0"> + {{ __('There seem to be no nodes available for this specification. Admins have been notified. Please try again later of contact us.') }} + </div> </div> </div> </div> From 3359204b2ddc9f2105c5dc0c2d0f7388d2607358 Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Thu, 23 May 2024 00:26:08 +0200 Subject: [PATCH 319/514] [Feature]: Rate-Limit verification emails --- app/Models/User.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/Models/User.php b/app/Models/User.php index 246cb0c5a..3da03cba6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -14,6 +14,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\RateLimiter; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\CausesActivity; use Spatie\Activitylog\Traits\LogsActivity; @@ -188,7 +189,19 @@ public function discordUser() public function sendEmailVerificationNotification() { - $this->notify(new QueuedVerifyEmail); + // Rate limit the email verification notification to 1 attempt per 30 minutes + $executed = RateLimiter::attempt( + key: 'verify-mail'. $this->id, + maxAttempts: 1, + callback: function() { + $this->notify(new QueuedVerifyEmail); + }, + decaySeconds: 1800 + ); + + if (! $executed) { + return response()->json(['message' => 'Too many requests, try again in: ' . RateLimiter::availableIn('send-message:'. $this->id) . ' seconds'], 429); + } } /** From 150c24d078b0de079f69056b7990e621f97d5d3b Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 23 May 2024 00:49:42 +0200 Subject: [PATCH 320/514] FIX: tickets works now --- app/Http/Controllers/TicketsController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index c2c9d14d4..5eb7f7e0b 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -19,6 +19,7 @@ use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Str; +use App\Settings\GeneralSettings; class TicketsController extends Controller { From 35643b208002a5321a2588ae280345b5cd32ac19 Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Thu, 23 May 2024 01:30:16 +0200 Subject: [PATCH 321/514] [Feature]: Send mail to admin (first user ID) when nodes are full --- app/Http/Controllers/ProductController.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index ae9c1548c..5f3a41cbc 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -7,11 +7,16 @@ use App\Models\Pterodactyl\Location; use App\Models\Pterodactyl\Node; use App\Models\Product; +use App\Models\User; +use App\Notifications\DynamicNotification; use App\Settings\PterodactylSettings; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Notification; +use Illuminate\Support\Facades\RateLimiter; class ProductController extends Controller { @@ -93,6 +98,19 @@ public function getLocationsBasedOnEgg(Request $request, Egg $egg) } }); + if($locations->isEmpty()){ + // Rate limit the node full notification to 1 attempt per 30 minutes + RateLimiter::attempt( + key: 'nodes-full-warning', + maxAttempts: 1, + callback: function() { + $user = User::find(1); // Get the first user, should be the admin + Notification::send($user,new DynamicNotification(['mail'], [], mail: (new MailMessage)->subject('Attention! All of the nodes are full!')->greeting('Attention!')->line('All nodes are full, please add more nodes'))); + }, + decaySeconds: 1800 + ); + } + return $locations; } From bf260528a0512a85ea1aecd23d9caa525082069b Mon Sep 17 00:00:00 2001 From: Jens <34610614+AGuyNamedJens@users.noreply.github.com> Date: Thu, 23 May 2024 01:46:38 +0200 Subject: [PATCH 322/514] Update User.php --- app/Models/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/User.php b/app/Models/User.php index 3da03cba6..9577fa808 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -200,7 +200,7 @@ public function sendEmailVerificationNotification() ); if (! $executed) { - return response()->json(['message' => 'Too many requests, try again in: ' . RateLimiter::availableIn('send-message:'. $this->id) . ' seconds'], 429); + return response()->json(['message' => 'Too many requests, try again in: ' . RateLimiter::availableIn('verify-mail:'. $this->id) . ' seconds'], 429); } } From f760cb689ca0907c011ee61115e2fd8118e83cfc Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Thu, 23 May 2024 01:54:22 +0200 Subject: [PATCH 323/514] [Feature]: Send mail to admin users(?) needs testing --- app/Http/Controllers/ProductController.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index 5f3a41cbc..4e7e09e56 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -104,10 +104,12 @@ public function getLocationsBasedOnEgg(Request $request, Egg $egg) key: 'nodes-full-warning', maxAttempts: 1, callback: function() { - $user = User::find(1); // Get the first user, should be the admin - Notification::send($user,new DynamicNotification(['mail'], [], mail: (new MailMessage)->subject('Attention! All of the nodes are full!')->greeting('Attention!')->line('All nodes are full, please add more nodes'))); + // get admin role and check users + $users = User::query()->where('role', '=', '1')->get(); + Notification::send($users,new DynamicNotification(['mail'],[], + mail: (new MailMessage)->subject('Attention! All of the nodes are full!')->greeting('Attention!')->line('All nodes are full, please add more nodes'))); }, - decaySeconds: 1800 + decaySeconds: 5 ); } From 924abc0b79431b0aeed2bb3ad474d4938e53c0e1 Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Thu, 23 May 2024 00:26:18 -0300 Subject: [PATCH 324/514] Add use MercadoPagoSettings and update $notificationId now I noticed that the use of MercadoPagoSettings was missing, The notificationId was changed to the correct values to be expected --- .../PaymentGateways/MercadoPago/MercadoPagoExtension.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php index ffd545d18..279f1572e 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -18,6 +18,7 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Http; use App\Notifications\ConfirmPaymentNotification; +use App\Extensions\PaymentGateways\MercadoPago\MercadoPagoSettings; /** * Summary of MercadoPagoExtension @@ -104,7 +105,7 @@ static function Webhook(Request $request): JsonResponse return response()->json(['success' => true]); } else { try { - $notificationId = $request->input('data.id') ?? $request->input('id') ?? $request->input('payment_id') ?? 'unknown'; + $notificationId = $laravelRequest->input('data_id') || $laravelRequest->input('data.id') || "unknown"; if ($notificationId == 'unknown') { return response()->json(['success' => false]); } else if ($notificationId == '123456') { From 2b8c40b852336d25884f6d10ad286b94bf21115d Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Thu, 23 May 2024 00:28:42 -0300 Subject: [PATCH 325/514] Update MercadoPagoExtension.php Sorry, incorrect variable name laravelRequest, --- .../PaymentGateways/MercadoPago/MercadoPagoExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php index 279f1572e..5bb1805f6 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -105,7 +105,7 @@ static function Webhook(Request $request): JsonResponse return response()->json(['success' => true]); } else { try { - $notificationId = $laravelRequest->input('data_id') || $laravelRequest->input('data.id') || "unknown"; + $notificationId = $request->input('data_id') || $request->input('data.id') || "unknown"; if ($notificationId == 'unknown') { return response()->json(['success' => false]); } else if ($notificationId == '123456') { From 02444770707a6cc4df31b647aef5a8b56cc2943a Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Thu, 23 May 2024 23:52:14 +0200 Subject: [PATCH 326/514] [Bug]: Fix ForgotPasswordController breaking --- app/Http/Controllers/Auth/ForgotPasswordController.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 79f1e99fd..6c3f34aef 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -32,13 +32,15 @@ public function __construct() $this->middleware('guest'); } - protected function validateEmail(Request $request, GeneralSettings $general_settings) + protected function validateEmail(Request $request) { $this->validate($request, [ 'email' => ['required', 'string', 'email', 'max:255'], ]); - if ($general_settings->recaptcha_enabled) { + $recaptcha_enabled = app(GeneralSettings::class)->recaptcha_enabled; + + if ($recaptcha_enabled) { $this->validate($request, [ 'g-recaptcha-response' => 'required|recaptcha', ]); From 4bf534ad50682c6fbd5c2226660b858fe43abed4 Mon Sep 17 00:00:00 2001 From: MrWeez <64205495+MrWeez@users.noreply.github.com> Date: Fri, 24 May 2024 10:38:31 +0300 Subject: [PATCH 327/514] Fix error 500 on the checkout page, small code refactor and total display fix (#963) * fix: replaced number_format by round because this caused error 500 on checkout * refactor: code has been made more readable * fix: incorrect display of total on checkout --- app/Models/ShopProduct.php | 10 +++++++--- themes/default/views/store/checkout.blade.php | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Models/ShopProduct.php b/app/Models/ShopProduct.php index 12b870a28..8e8a2260b 100644 --- a/app/Models/ShopProduct.php +++ b/app/Models/ShopProduct.php @@ -80,7 +80,9 @@ public function getTaxPercent() public function getPriceAfterDiscount() { - return number_format($this->price - ($this->price * PartnerDiscount::getDiscount() / 100), 2); + $discountRate = PartnerDiscount::getDiscount() / 100; + $discountedPrice = $this->price * (1 - $discountRate); + return round($discountedPrice, 2); } /** @@ -90,7 +92,8 @@ public function getPriceAfterDiscount() */ public function getTaxValue() { - return number_format($this->getPriceAfterDiscount() * $this->getTaxPercent() / 100, 2); + $taxValue = $this->getPriceAfterDiscount() * $this->getTaxPercent() / 100; + return round($taxValue, 2); } /** @@ -100,6 +103,7 @@ public function getTaxValue() */ public function getTotalPrice() { - return number_format($this->getPriceAfterDiscount() + $this->getTaxValue(), 2); + $total = $this->getPriceAfterDiscount() + $this->getTaxValue(); + return round($total, 2); } } diff --git a/themes/default/views/store/checkout.blade.php b/themes/default/views/store/checkout.blade.php index b0f91f0de..6a0d28b2c 100644 --- a/themes/default/views/store/checkout.blade.php +++ b/themes/default/views/store/checkout.blade.php @@ -237,7 +237,7 @@ function couponForm() { payment_method: '', coupon_code: '', submitted: false, - totalPrice: {{ $discountedprice }}, + totalPrice: {{ $total }}, couponDiscountedValue: 0, From 17d2aad0c98ab57a296ec9280e388f5dc143e932 Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Fri, 24 May 2024 10:05:28 +0200 Subject: [PATCH 328/514] [bug]: Fix descriptions overflowing (adjusted to Pterodactyl's Description) [Feature]: Location description label on server create (optional setting) --- ...41233_update_description_servers_table.php | 32 +++++++++++++++++++ ...551_update_description_locations_table.php | 32 +++++++++++++++++++ ..._22_152306_add_location_settings_table.php | 16 ++++++++++ 3 files changed, 80 insertions(+) create mode 100644 database/migrations/2024_05_22_141233_update_description_servers_table.php create mode 100644 database/migrations/2024_05_22_143551_update_description_locations_table.php create mode 100644 database/settings/2024_05_22_152306_add_location_settings_table.php diff --git a/database/migrations/2024_05_22_141233_update_description_servers_table.php b/database/migrations/2024_05_22_141233_update_description_servers_table.php new file mode 100644 index 000000000..e0964c529 --- /dev/null +++ b/database/migrations/2024_05_22_141233_update_description_servers_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('servers', function (Blueprint $table) { + $table->text('description')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->string('description')->nullable()->change(); + }); + } +}; diff --git a/database/migrations/2024_05_22_143551_update_description_locations_table.php b/database/migrations/2024_05_22_143551_update_description_locations_table.php new file mode 100644 index 000000000..e2b70688b --- /dev/null +++ b/database/migrations/2024_05_22_143551_update_description_locations_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('locations', function (Blueprint $table) { + $table->text('description')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('locations', function (Blueprint $table) { + $table->string('description')->change(); + }); + } +}; diff --git a/database/settings/2024_05_22_152306_add_location_settings_table.php b/database/settings/2024_05_22_152306_add_location_settings_table.php new file mode 100644 index 000000000..aaa01e040 --- /dev/null +++ b/database/settings/2024_05_22_152306_add_location_settings_table.php @@ -0,0 +1,16 @@ +<?php + +use Spatie\LaravelSettings\Migrations\SettingsMigration; + +return new class extends SettingsMigration +{ + public function up(): void + { + $this->migrator->add('server.location_description_enabled',false); + } + + public function down(): void + { + $this->migrator->delete('server.location_description_enabled'); + } +}; From f57ee4674dce3fd6305a59e77fcee8c0daaff291 Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Sat, 25 May 2024 18:42:49 -0300 Subject: [PATCH 329/514] Final organization of resources I changed a little the way the Webhook responds to the MercadoPago, better filtering requests and expected values, I also added to getRedirectUrl a check necessary for the MercadoPago Webhook to work. --- .../MercadoPago/MercadoPagoExtension.php | 63 ++++++++++++------- lang/en.json | 1 + 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php index 5bb1805f6..82a067722 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -39,6 +39,15 @@ public static function getConfig(): array public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct, string $totalPriceString): string { + /** + * For Mercado Pago to work correctly, + * it is necessary to use SSL and the app.url must start with "https://", + * this is necessary so that the webhook receives the information and not an error. + */ + if (!str_contains(config('app.url'), 'https://')) { + throw new Exception(__('It is not possible to purchase via MercadoPago: APP_URL does not have HTTPS, required by Mercado Pago.')); + } + $user = Auth::user(); $user = User::findOrFail($user->id); $url = 'https://api.mercadopago.com/checkout/preferences'; @@ -61,7 +70,6 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct [ 'title' => "Order #{$payment->id} - " . $shopProduct->name, 'quantity' => 1, - // convert to float 'unit_price' => floatval($totalPriceString), 'currency_id' => $shopProduct->currency_code, ], @@ -90,42 +98,50 @@ static function Success(Request $request): void $payment = Payment::findOrFail($request->input('payment')); $payment->status = PaymentStatus::PROCESSING; $payment->save(); - Redirect::route('home')->with('success', 'Your payment is being processed')->send(); + Redirect::route('home')->with('success', 'Your payment is being processed!')->send(); return; } static function Webhook(Request $request): JsonResponse { $topic = $request->input('topic'); - if ($topic === 'merchant_order') { - // ignore other types IPN - return response()->json(['success' => true]); - } else if ($topic === 'payment') { - // ignore other types IPN + $action = $request->input('action'); + + /** + * Mercado Pago sends several requests for information in the webhook, + * but most are for other types of API, and that is why it is filtered here. + */ + if ($topic && ($topic === 'merchant_order' || $topic === 'payment')) { return response()->json(['success' => true]); - } else { - try { - $notificationId = $request->input('data_id') || $request->input('data.id') || "unknown"; - if ($notificationId == 'unknown') { - return response()->json(['success' => false]); - } else if ($notificationId == '123456') { - // mercado pago api test - return response()->json(['success' => true]); - } else { + } + + try { + if($action) { + $notification = $request['data']['id']; + + // Filter the API for payments + if (!$notification || !$action) return response()->json(['success' => false], 400); + // Mercado pago test api, for testing webhook request + if ($notification == '123456') return response()->json(['success' => true], 200); + + /** + * Check action have payment.*, + * what is expected for this type of api + */ + if (str_contains($action, 'payment')) { $url = "https://api.mercadopago.com/v1/payments/" . $notificationId; $settings = new MercadoPagoSettings(); $response = Http::withHeaders([ 'Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $settings->access_token, ])->get($url); - if ($response->successful()) { $mercado = $response->json(); $status = $mercado['status']; $payment = Payment::findOrFail($mercado['metadata']['crtl_panel_payment_id']); $shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id); if ($status == "approved") { - // avoids double additions, if the user enters after the webhook has already added the credits + // Avoid double addition of credits, whether due to double requests from the paid market, or a malicious user if ($payment->status !== PaymentStatus::PAID) { $user = User::findOrFail($payment->user_id); $payment->status = PaymentStatus::PAID; @@ -145,12 +161,17 @@ static function Webhook(Request $request): JsonResponse $payment->save(); event(new PaymentEvent($user, $payment, $shopProduct)); } + return response()->json(['success' => true]); + } else { + return response()->json(['success' => false]); } + } else { + return response()->json(['success' => false]); } - } catch (Exception $ex) { - Log::error('MercadoPago Webhook(IPN) Payment: ' . $ex->getMessage()); - return response()->json(['success' => false]); } + } catch (Exception $ex) { + Log::error('MercadoPago Webhook(IPN) Payment: ' . $ex->getMessage()); + return response()->json(['success' => false]); } return response()->json(['success' => true]); } diff --git a/lang/en.json b/lang/en.json index 633a4ec2c..070a6eb1e 100644 --- a/lang/en.json +++ b/lang/en.json @@ -296,6 +296,7 @@ "Everyone": "Everyone", "Clients": "Clients", "Enable Ticketsystem": "Enable Ticketsystem", + "It is not possible to purchase via MercadoPago: APP_URL does not have HTTPS, required by Mercado Pago":"It is not possible to purchase via MercadoPago: APP_URL does not have HTTPS, required by Mercado Pago", "PayPal Client-ID": "PayPal Client-ID", "PayPal Secret-Key": "PayPal Secret-Key", "PayPal Sandbox Client-ID": "PayPal Sandbox Client-ID", From 400434eb18abf1dbe816254d65549ccca800464a Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 09:49:34 +0200 Subject: [PATCH 330/514] ADD: more feature to the main readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 291c86cff..48ae18dad 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ CtrlPanel offers an easy-to-use and free billing solution for all starting and e - Account Management - Admin Dashboard and Tools - Addon Support -- and more! +- Multiple Languages Supported +- and More! ## ⛰️ Live Demo From 35a4e35830f54eb83a674178252094a6d5b42e24 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 09:49:52 +0200 Subject: [PATCH 331/514] ADD: discord link to main readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48ae18dad..9c5dcfb90 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Roadmap: [CtrlPanel Roadmap](https://github.com/orgs/Ctrlpanel-gg/projects/1) ## 🗣️ Discussion / Ask for Help -For any general or technical questions, join CtrlPanel Discord for finding answers to your question. If you cannot find the information you need, feel free to ask. +For any general or technical questions, join CtrlPanel [Discord](https://discord.gg/4Y6HjD2uyU) for finding answers to your question. If you cannot find the information you need, feel free to ask. ## 🤝 Contributing From cb7acf9b5181afaba7a933f606f65baa8fe800ab Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 09:50:16 +0200 Subject: [PATCH 332/514] UPDATE: updated license --- LICENSE | 682 ++++++++++++++++++++++++++++++- contributor_license_agreement.md | 96 +++++ 2 files changed, 757 insertions(+), 21 deletions(-) create mode 100644 contributor_license_agreement.md diff --git a/LICENSE b/LICENSE index 00e2a0b66..29ebfa545 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,661 @@ -MIT License - -Copyright (c) 2021 CtrlPanel.gg - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +<https://www.gnu.org/licenses/>. \ No newline at end of file diff --git a/contributor_license_agreement.md b/contributor_license_agreement.md new file mode 100644 index 000000000..05df29748 --- /dev/null +++ b/contributor_license_agreement.md @@ -0,0 +1,96 @@ +Thank you for your interest in CtrlPanel-gg ("CtrlPanel Developers"). To clarify the intellectual property license +granted with Contributions from any person or entity, the CtrlPanel Developers +must have on file a signed Contributor License Agreement ("CLA") +from each Contributor, indicating agreement with the license +terms below. This agreement is for your protection as a Contributor +as well as the protection of the CtrlPanel Developers and its users. It does not +change your rights to use your own Contributions for any other purpose. + +You accept and agree to the following terms and conditions for Your +Contributions (present and future) that you submit to the CtrlPanel Developers. In +return, the CtrlPanel Developers shall not use Your Contributions in a way that +is contrary to the public benefit or inconsistent with its nonprofit +status and bylaws in effect at the time of the Contribution. Except +for the license granted herein to the CtrlPanel Developers and recipients of +software distributed by the CtrlPanel Developers, You reserve all right, title, +and interest in and to Your Contributions. +1. Definitions. + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with the CtrlPanel Developers. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to the CtrlPanel Developers for inclusion + in, or documentation of, any of the products owned or managed by + the CtrlPanel Developers (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to the CtrlPanel Developers or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, the CtrlPanel Developers for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to the CtrlPanel Developers and to + recipients of software distributed by the CtrlPanel Developers a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to the CtrlPanel Developers and to + recipients of software distributed by the CtrlPanel Developers a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to the CtrlPanel Developers, or that your employer has + executed a separate Corporate CLA with the CtrlPanel Developers. +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. +7. Should You wish to submit work that is not Your original creation, + You may submit it to the CtrlPanel Developers separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". +8. You agree to notify the CtrlPanel Developers of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. From 4a8ba2e5c8f4c2d02d12a19afb97de2817761a10 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 10:04:52 +0200 Subject: [PATCH 333/514] UPDATE: contributing.md --- .github/CONTRIBUTING.md | 32 ++++++++++++++++++-------------- lang/README.md | 10 ++++++++++ 2 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 lang/README.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 72eb32a7b..6f0a54f9e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,29 +6,33 @@ Thank you for considering contributing to this repository! Before making a contr Check the open issues to see if there's something you can contribute to. If you have an idea or encounter a bug that's not already listed, feel free to create a new issue and wait for feedback from the development team. -## 🤝 Code of Conduct +## 🤝 Code of Conduct & Contributor License -Please adhere to our [Code of Conduct](https://github.com/Ctrlpanel-gg/panel/blob/main/.github/CODE_OF_CONDUCT.md) in all your interactions with the project. +Please adhere to our [Code of Conduct](https://github.com/Ctrlpanel-gg/panel/blob/main/.github/CODE_OF_CONDUCT.md) and our [Contributor License](https://github.com/Ctrlpanel-gg/panel/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md) in all your interactions with the project. ## 🌍 Localization -If you add any strings that are displayed on the frontend, please localize them using the following format: -``` -"New String" -> {{ __('New String') }} -``` -After adding localized strings, run the following command to generate localization files: -```cmd -php artisan translatable:export en -``` +Please read our [Localization Guide](https://github.com/Ctrlpanel-gg/panel/blob/main/lang/README.md) on how to manage and add localization to the project. ## 🚀 Pull Request Process -1. Give your pull request (PR) a clear and descriptive title that summarizes the changes. -2. The development team will review your code and provide feedback or approve/merge it when appropriate. -3. Ensure that your PR follows our Code of Conduct and coding style guidelines. +1. Provide a clear and descriptive title for your pull request (PR) summarizing the changes. +2. If your PR is not yet finished, correctly mark it as a draft and mention any errors it's correcting. +3. The development team will review your code and offer feedback or approve/merge it as necessary. +4. Ensure that your PR adheres to our Code of Conduct and coding style guidelines. +5. Test your changes thoroughly to ensure they function as expected. +6. Include relevant documentation updates if applicable. +7. Address any review comments promptly to expedite the review process. ### 💻 Coding Style -We follow the PSR12 code standard for PHP. +We adhere to the PSR12 code standard for PHP. + +- Follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) commit norms. +- Make clear and concise commits. +- Name your PR branch as [action]/what-you-are-doing. +- Make clear commits, one per action, and include comments. + +⚠️ **Important Note:** The owner of the project has the final decision, and the development team of CtrlPanel reserves the right to close incorrect PRs. PRs that remain inactive or invalid for an extended period may also be subject to closure. Thank you for your contributions! 🎉 diff --git a/lang/README.md b/lang/README.md new file mode 100644 index 000000000..89edaeb97 --- /dev/null +++ b/lang/README.md @@ -0,0 +1,10 @@ +# 🌍 Localization + +If you add any strings that are displayed on the frontend, please localize them using the following format: +``` +"New String" -> {{ __('New String') }} +``` +After adding localized strings, run the following command to generate localization files: +```cmd +php artisan translatable:export en +``` From 2c2ec5e776567f27d6dc849b2ee399f9de6a5c8e Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 10:08:10 +0200 Subject: [PATCH 334/514] UPDATE: updated the bug and feature template to not include default title --- .github/ISSUE_TEMPLATE/bug.yml | 1 - .github/ISSUE_TEMPLATE/feature.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index f9e8e9ec3..b823ace8a 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -1,6 +1,5 @@ name: "\U0001F41B Bug report" description: Create a report to help us improve -title: "[Bug] " labels: ["bug"] body: - type: textarea diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index 6d628f97c..e61d14909 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -1,6 +1,5 @@ name: "\U0001F680 Feature request" description: Suggest a feature or idea for this project -title: "[Feature] " labels: ["feature"] body: - type: textarea From be595e904744d257789f14f70718d16b65688232 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 10:08:31 +0200 Subject: [PATCH 335/514] FIX: some error in the github documentations --- .github/CONTRIBUTING.md | 1 - .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/SECURITY.md | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6f0a54f9e..c00a9e604 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -29,7 +29,6 @@ Please read our [Localization Guide](https://github.com/Ctrlpanel-gg/panel/blob/ We adhere to the PSR12 code standard for PHP. - Follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) commit norms. -- Make clear and concise commits. - Name your PR branch as [action]/what-you-are-doing. - Make clear commits, one per action, and include comments. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 89224a428..7da279d75 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,6 @@ ✨ Thank you for your contribution to our project! Before you submit your pull request, please take a moment to review and complete the following -⚠️ Please modify this template below and if not already done, read our pull request rules, Thanks! +⚠️ Please modify this template below and if not already done, read our contributing rules in .github folder, Thanks! Ensure that your pull request meets the following criteria: diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 278e81e71..d53e5a6eb 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -12,6 +12,8 @@ At this time, we only accept vulnerability reports through GitHub Advisories. We ## Supported Versions +- Latests + ### ControlPanel Versions We strongly recommend using or upgrading to the latest version of ControlPanel to ensure you have access to the latest security fixes and enhancements. From e52df329cfb93ed13e8cf3866d88c267bdff887b Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 10:29:28 +0200 Subject: [PATCH 336/514] FIX: trying to fix a strange issue whit the CLA --- .../CONTRIBUTOR_LICENSE_AGREEMENT.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contributor_license_agreement.md => .github/CONTRIBUTOR_LICENSE_AGREEMENT.md (100%) diff --git a/contributor_license_agreement.md b/.github/CONTRIBUTOR_LICENSE_AGREEMENT.md similarity index 100% rename from contributor_license_agreement.md rename to .github/CONTRIBUTOR_LICENSE_AGREEMENT.md From 487a7f1c8e0cca5faa10e3d4afdae3d9a98ab1d5 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 10:29:46 +0200 Subject: [PATCH 337/514] FIX: fixed the strange issue whit the CLA --- ...BUTOR_LICENSE_AGREEMENT.md => CONTRIBUTOR_LICENSE_AGREEMENT.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/CONTRIBUTOR_LICENSE_AGREEMENT.md => CONTRIBUTOR_LICENSE_AGREEMENT.md (100%) diff --git a/.github/CONTRIBUTOR_LICENSE_AGREEMENT.md b/CONTRIBUTOR_LICENSE_AGREEMENT.md similarity index 100% rename from .github/CONTRIBUTOR_LICENSE_AGREEMENT.md rename to CONTRIBUTOR_LICENSE_AGREEMENT.md From 5dc6aabba81ebbde6122d47187f8dd2aa0d0e5b3 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 10:55:06 +0200 Subject: [PATCH 338/514] UPDATED: updated the development docker to use the standalone one whit more tools --- docker/development/docker-compose.yml | 44 ++- docker/development/nginx/Dockerfile | 10 - docker/development/nginx/default.conf | 20 -- docker/development/nginx/nginx.conf | 29 -- docker/development/php/Dockerfile | 18 -- docker/development/php/www.conf | 439 -------------------------- 6 files changed, 21 insertions(+), 539 deletions(-) delete mode 100644 docker/development/nginx/Dockerfile delete mode 100644 docker/development/nginx/default.conf delete mode 100644 docker/development/nginx/nginx.conf delete mode 100644 docker/development/php/Dockerfile delete mode 100644 docker/development/php/www.conf diff --git a/docker/development/docker-compose.yml b/docker/development/docker-compose.yml index 16c9bf94f..4d31230a7 100644 --- a/docker/development/docker-compose.yml +++ b/docker/development/docker-compose.yml @@ -1,21 +1,20 @@ version: '3' -networks: - laravel: - +// TODO: add wings and pterodactyl services: - nginx: + + controlpanel_standalone: build: context: ../../ - dockerfile: docker/development/nginx/Dockerfile - container_name: controlpanel_nginx + dockerfile: ./docker/standalone/Dockerfile + container_name: controlpanel_standalone + restart: on-failure ports: - - 80:80 + - "80:80" + - "443:443" volumes: - - "../../:/var/www/html" - depends_on: - - php - - mysql + - './website_files:/var/www/html:rw' # change it to your project + - './nginx_config:/etc/nginx/conf.d/:rw' # change it networks: - laravel @@ -36,16 +35,6 @@ services: networks: - laravel - php: - build: - context: ../../ - dockerfile: docker/development/php/Dockerfile - container_name: controlpanel_php - volumes: - - "../../:/var/www/html" - networks: - - laravel - phpmyadmin: image: phpmyadmin/phpmyadmin container_name: controlpanel_phpmyadmin @@ -61,5 +50,14 @@ services: networks: - laravel -volumes: - mysql: \ No newline at end of file + redis: + image: redis + container_name: controlpanel_redis + restart: unless-stopped + ports: + - "6379:6379" + networks: + - laravel + +networks: + laravel: diff --git a/docker/development/nginx/Dockerfile b/docker/development/nginx/Dockerfile deleted file mode 100644 index deca865f1..000000000 --- a/docker/development/nginx/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM nginx:stable-alpine - -RUN addgroup -g 1000 laravel && adduser -G laravel -g laravel -s /bin/sh -D laravel - -ADD ./docker/development/nginx/nginx.conf /etc/nginx/ -ADD ./docker/development/nginx/default.conf /etc/nginx/conf.d/ - -RUN mkdir -p /var/www/html - -RUN chown laravel:laravel /var/www/html diff --git a/docker/development/nginx/default.conf b/docker/development/nginx/default.conf deleted file mode 100644 index 282633d76..000000000 --- a/docker/development/nginx/default.conf +++ /dev/null @@ -1,20 +0,0 @@ -server { - listen 80; - index index.php index.html; - server_name _; - root /var/www/html/public; - - location / { - try_files $uri $uri/ /index.php?$query_string; - } - - location ~ \.php$ { - try_files $uri =404; - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass php:9000; - fastcgi_index index.php; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param PATH_INFO $fastcgi_path_info; - } -} diff --git a/docker/development/nginx/nginx.conf b/docker/development/nginx/nginx.conf deleted file mode 100644 index 060e50253..000000000 --- a/docker/development/nginx/nginx.conf +++ /dev/null @@ -1,29 +0,0 @@ -user laravel; -worker_processes auto; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - include /etc/nginx/conf.d/*.conf; -} diff --git a/docker/development/php/Dockerfile b/docker/development/php/Dockerfile deleted file mode 100644 index 3ec761ae7..000000000 --- a/docker/development/php/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM php:8.1-fpm-buster -RUN apt-get update \ - && apt-get install -y build-essential zlib1g-dev default-mysql-client curl gnupg procps vim git unzip libzip-dev libpq-dev libicu-dev libonig-dev libpng-dev libjpeg-dev libfreetype6-dev - -RUN docker-php-ext-install mysqli pdo pdo_mysql intl zip gd bcmath - -ADD ./docker/development/php/www.conf /usr/local/etc/php-fpm.d/ - -RUN mkdir -p /var/www/html - -RUN addgroup --gid 1000 laravel && adduser --ingroup laravel --uid 1000 --shell /bin/sh --disabled-password --gecos "" laravel -RUN chown laravel:laravel /var/www/html - -WORKDIR /var/www/html - -USER laravel - -COPY --from=composer:latest /usr/bin/composer /usr/bin/composer diff --git a/docker/development/php/www.conf b/docker/development/php/www.conf deleted file mode 100644 index eb028e111..000000000 --- a/docker/development/php/www.conf +++ /dev/null @@ -1,439 +0,0 @@ -; Start a new pool named 'www'. -; the variable $pool can be used in any directive and will be replaced by the -; pool name ('www' here) -[www] - -; Per pool prefix -; It only applies on the following directives: -; - 'access.log' -; - 'slowlog' -; - 'listen' (unixsocket) -; - 'chroot' -; - 'chdir' -; - 'php_values' -; - 'php_admin_values' -; When not set, the global prefix (or NONE) applies instead. -; Note: This directive can also be relative to the global prefix. -; Default Value: none -;prefix = /path/to/pools/$pool - -; Unix user/group of processes -; Note: The user is mandatory. If the group is not set, the default user's group -; will be used. -user = laravel -group = laravel - -; The address on which to accept FastCGI requests. -; Valid syntaxes are: -; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on -; a specific port; -; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on -; a specific port; -; 'port' - to listen on a TCP socket to all addresses -; (IPv6 and IPv4-mapped) on a specific port; -; '/path/to/unix/socket' - to listen on a unix socket. -; Note: This value is mandatory. -listen = 127.0.0.1:9000 - -; Set listen(2) backlog. -; Default Value: 511 (-1 on FreeBSD and OpenBSD) -;listen.backlog = 511 - -; Set permissions for unix socket, if one is used. In Linux, read/write -; permissions must be set in order to allow connections from a web server. Many -; BSD-derived systems allow connections regardless of permissions. The owner -; and group can be specified either by name or by their numeric IDs. -; Default Values: user and group are set as the running user -; mode is set to 0660 -;listen.owner = www-data -;listen.group = www-data -;listen.mode = 0660 -; When POSIX Access Control Lists are supported you can set them using -; these options, value is a comma separated list of user/group names. -; When set, listen.owner and listen.group are ignored -;listen.acl_users = -;listen.acl_groups = - -; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect. -; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original -; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address -; must be separated by a comma. If this value is left blank, connections will be -; accepted from any ip address. -; Default Value: any -;listen.allowed_clients = 127.0.0.1 - -; Specify the nice(2) priority to apply to the pool processes (only if set) -; The value can vary from -19 (highest priority) to 20 (lower priority) -; Note: - It will only work if the FPM master process is launched as root -; - The pool processes will inherit the master process priority -; unless it specified otherwise -; Default Value: no set -; process.priority = -19 - -; Set the process dumpable flag (PR_SET_DUMPABLE prctl) even if the process user -; or group is differrent than the master process user. It allows to create process -; core dump and ptrace the process for the pool user. -; Default Value: no -; process.dumpable = yes - -; Choose how the process manager will control the number of child processes. -; Possible Values: -; static - a fixed number (pm.max_children) of child processes; -; dynamic - the number of child processes are set dynamically based on the -; following directives. With this process management, there will be -; always at least 1 children. -; pm.max_children - the maximum number of children that can -; be alive at the same time. -; pm.start_servers - the number of children created on startup. -; pm.min_spare_servers - the minimum number of children in 'idle' -; state (waiting to process). If the number -; of 'idle' processes is less than this -; number then some children will be created. -; pm.max_spare_servers - the maximum number of children in 'idle' -; state (waiting to process). If the number -; of 'idle' processes is greater than this -; number then some children will be killed. -; ondemand - no children are created at startup. Children will be forked when -; new requests will connect. The following parameter are used: -; pm.max_children - the maximum number of children that -; can be alive at the same time. -; pm.process_idle_timeout - The number of seconds after which -; an idle process will be killed. -; Note: This value is mandatory. -pm = dynamic - -; The number of child processes to be created when pm is set to 'static' and the -; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. -; This value sets the limit on the number of simultaneous requests that will be -; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. -; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP -; CGI. The below defaults are based on a server without much resources. Don't -; forget to tweak pm.* to fit your needs. -; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' -; Note: This value is mandatory. -pm.max_children = 5 - -; The number of child processes created on startup. -; Note: Used only when pm is set to 'dynamic' -; Default Value: (min_spare_servers + max_spare_servers) / 2 -pm.start_servers = 2 - -; The desired minimum number of idle server processes. -; Note: Used only when pm is set to 'dynamic' -; Note: Mandatory when pm is set to 'dynamic' -pm.min_spare_servers = 1 - -; The desired maximum number of idle server processes. -; Note: Used only when pm is set to 'dynamic' -; Note: Mandatory when pm is set to 'dynamic' -pm.max_spare_servers = 3 - -; The number of seconds after which an idle process will be killed. -; Note: Used only when pm is set to 'ondemand' -; Default Value: 10s -;pm.process_idle_timeout = 10s; - -; The number of requests each child process should execute before respawning. -; This can be useful to work around memory leaks in 3rd party libraries. For -; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. -; Default Value: 0 -;pm.max_requests = 500 - -; The URI to view the FPM status page. If this value is not set, no URI will be -; recognized as a status page. It shows the following informations: -; pool - the name of the pool; -; process manager - static, dynamic or ondemand; -; start time - the date and time FPM has started; -; start since - number of seconds since FPM has started; -; accepted conn - the number of request accepted by the pool; -; listen queue - the number of request in the queue of pending -; connections (see backlog in listen(2)); -; max listen queue - the maximum number of requests in the queue -; of pending connections since FPM has started; -; listen queue len - the size of the socket queue of pending connections; -; idle processes - the number of idle processes; -; active processes - the number of active processes; -; total processes - the number of idle + active processes; -; max active processes - the maximum number of active processes since FPM -; has started; -; max children reached - number of times, the process limit has been reached, -; when pm tries to start more children (works only for -; pm 'dynamic' and 'ondemand'); -; Value are updated in real time. -; Example output: -; pool: www -; process manager: static -; start time: 01/Jul/2011:17:53:49 +0200 -; start since: 62636 -; accepted conn: 190460 -; listen queue: 0 -; max listen queue: 1 -; listen queue len: 42 -; idle processes: 4 -; active processes: 11 -; total processes: 15 -; max active processes: 12 -; max children reached: 0 -; -; By default the status page output is formatted as text/plain. Passing either -; 'html', 'xml' or 'json' in the query string will return the corresponding -; output syntax. Example: -; http://www.foo.bar/status -; http://www.foo.bar/status?json -; http://www.foo.bar/status?html -; http://www.foo.bar/status?xml -; -; By default the status page only outputs short status. Passing 'full' in the -; query string will also return status for each pool process. -; Example: -; http://www.foo.bar/status?full -; http://www.foo.bar/status?json&full -; http://www.foo.bar/status?html&full -; http://www.foo.bar/status?xml&full -; The Full status returns for each process: -; pid - the PID of the process; -; state - the state of the process (Idle, Running, ...); -; start time - the date and time the process has started; -; start since - the number of seconds since the process has started; -; requests - the number of requests the process has served; -; request duration - the duration in µs of the requests; -; request method - the request method (GET, POST, ...); -; request URI - the request URI with the query string; -; content length - the content length of the request (only with POST); -; user - the user (PHP_AUTH_USER) (or '-' if not set); -; script - the main script called (or '-' if not set); -; last request cpu - the %cpu the last request consumed -; it's always 0 if the process is not in Idle state -; because CPU calculation is done when the request -; processing has terminated; -; last request memory - the max amount of memory the last request consumed -; it's always 0 if the process is not in Idle state -; because memory calculation is done when the request -; processing has terminated; -; If the process is in Idle state, then informations are related to the -; last request the process has served. Otherwise informations are related to -; the current request being served. -; Example output: -; ************************ -; pid: 31330 -; state: Running -; start time: 01/Jul/2011:17:53:49 +0200 -; start since: 63087 -; requests: 12808 -; request duration: 1250261 -; request method: GET -; request URI: /test_mem.php?N=10000 -; content length: 0 -; user: - -; script: /home/fat/web/docs/php/test_mem.php -; last request cpu: 0.00 -; last request memory: 0 -; -; Note: There is a real-time FPM status monitoring sample web page available -; It's available in: /usr/local/share/php/fpm/status.html -; -; Note: The value must start with a leading slash (/). The value can be -; anything, but it may not be a good idea to use the .php extension or it -; may conflict with a real PHP file. -; Default Value: not set -;pm.status_path = /status - -; The ping URI to call the monitoring page of FPM. If this value is not set, no -; URI will be recognized as a ping page. This could be used to test from outside -; that FPM is alive and responding, or to -; - create a graph of FPM availability (rrd or such); -; - remove a server from a group if it is not responding (load balancing); -; - trigger alerts for the operating team (24/7). -; Note: The value must start with a leading slash (/). The value can be -; anything, but it may not be a good idea to use the .php extension or it -; may conflict with a real PHP file. -; Default Value: not set -;ping.path = /ping - -; This directive may be used to customize the response of a ping request. The -; response is formatted as text/plain with a 200 response code. -; Default Value: pong -;ping.response = pong - -; The access log file -; Default: not set -;access.log = log/$pool.access.log - -; The access log format. -; The following syntax is allowed -; %%: the '%' character -; %C: %CPU used by the request -; it can accept the following format: -; - %{user}C for user CPU only -; - %{system}C for system CPU only -; - %{total}C for user + system CPU (default) -; %d: time taken to serve the request -; it can accept the following format: -; - %{seconds}d (default) -; - %{miliseconds}d -; - %{mili}d -; - %{microseconds}d -; - %{micro}d -; %e: an environment variable (same as $_ENV or $_SERVER) -; it must be associated with embraces to specify the name of the env -; variable. Some exemples: -; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e -; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e -; %f: script filename -; %l: content-length of the request (for POST request only) -; %m: request method -; %M: peak of memory allocated by PHP -; it can accept the following format: -; - %{bytes}M (default) -; - %{kilobytes}M -; - %{kilo}M -; - %{megabytes}M -; - %{mega}M -; %n: pool name -; %o: output header -; it must be associated with embraces to specify the name of the header: -; - %{Content-Type}o -; - %{X-Powered-By}o -; - %{Transfert-Encoding}o -; - .... -; %p: PID of the child that serviced the request -; %P: PID of the parent of the child that serviced the request -; %q: the query string -; %Q: the '?' character if query string exists -; %r: the request URI (without the query string, see %q and %Q) -; %R: remote IP address -; %s: status (response code) -; %t: server time the request was received -; it can accept a strftime(3) format: -; %d/%b/%Y:%H:%M:%S %z (default) -; The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag -; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t -; %T: time the log has been written (the request has finished) -; it can accept a strftime(3) format: -; %d/%b/%Y:%H:%M:%S %z (default) -; The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag -; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t -; %u: remote user -; -; Default: "%R - %u %t \"%m %r\" %s" -;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" - -; The log file for slow requests -; Default Value: not set -; Note: slowlog is mandatory if request_slowlog_timeout is set -;slowlog = log/$pool.log.slow - -; The timeout for serving a single request after which a PHP backtrace will be -; dumped to the 'slowlog' file. A value of '0s' means 'off'. -; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) -; Default Value: 0 -;request_slowlog_timeout = 0 - -; Depth of slow log stack trace. -; Default Value: 20 -;request_slowlog_trace_depth = 20 - -; The timeout for serving a single request after which the worker process will -; be killed. This option should be used when the 'max_execution_time' ini option -; does not stop script execution for some reason. A value of '0' means 'off'. -; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) -; Default Value: 0 -;request_terminate_timeout = 0 - -; The timeout set by 'request_terminate_timeout' ini option is not engaged after -; application calls 'fastcgi_finish_request' or when application has finished and -; shutdown functions are being called (registered via register_shutdown_function). -; This option will enable timeout limit to be applied unconditionally -; even in such cases. -; Default Value: no -;request_terminate_timeout_track_finished = no - -; Set open file descriptor rlimit. -; Default Value: system defined value -;rlimit_files = 1024 - -; Set max core size rlimit. -; Possible Values: 'unlimited' or an integer greater or equal to 0 -; Default Value: system defined value -;rlimit_core = 0 - -; Chroot to this directory at the start. This value must be defined as an -; absolute path. When this value is not set, chroot is not used. -; Note: you can prefix with '$prefix' to chroot to the pool prefix or one -; of its subdirectories. If the pool prefix is not set, the global prefix -; will be used instead. -; Note: chrooting is a great security feature and should be used whenever -; possible. However, all PHP paths will be relative to the chroot -; (error_log, sessions.save_path, ...). -; Default Value: not set -;chroot = - -; Chdir to this directory at the start. -; Note: relative path can be used. -; Default Value: current directory or / when chroot -;chdir = /var/www - -; Redirect worker stdout and stderr into main error log. If not set, stdout and -; stderr will be redirected to /dev/null according to FastCGI specs. -; Note: on highloaded environement, this can cause some delay in the page -; process time (several ms). -; Default Value: no -;catch_workers_output = yes - -; Decorate worker output with prefix and suffix containing information about -; the child that writes to the log and if stdout or stderr is used as well as -; log level and time. This options is used only if catch_workers_output is yes. -; Settings to "no" will output data as written to the stdout or stderr. -; Default value: yes -;decorate_workers_output = no - -; Clear environment in FPM workers -; Prevents arbitrary environment variables from reaching FPM worker processes -; by clearing the environment in workers before env vars specified in this -; pool configuration are added. -; Setting to "no" will make all environment variables available to PHP code -; via getenv(), $_ENV and $_SERVER. -; Default Value: yes -;clear_env = no - -; Limits the extensions of the main script FPM will allow to parse. This can -; prevent configuration mistakes on the web server side. You should only limit -; FPM to .php extensions to prevent malicious users to use other extensions to -; execute php code. -; Note: set an empty value to allow all extensions. -; Default Value: .php -;security.limit_extensions = .php .php3 .php4 .php5 .php7 - -; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from -; the current environment. -; Default Value: clean env -;env[HOSTNAME] = $HOSTNAME -;env[PATH] = /usr/local/bin:/usr/bin:/bin -;env[TMP] = /tmp -;env[TMPDIR] = /tmp -;env[TEMP] = /tmp - -; Additional php.ini defines, specific to this pool of workers. These settings -; overwrite the values previously defined in the php.ini. The directives are the -; same as the PHP SAPI: -; php_value/php_flag - you can set classic ini defines which can -; be overwritten from PHP call 'ini_set'. -; php_admin_value/php_admin_flag - these directives won't be overwritten by -; PHP call 'ini_set' -; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no. - -; Defining 'extension' will load the corresponding shared extension from -; extension_dir. Defining 'disable_functions' or 'disable_classes' will not -; overwrite previously defined php.ini values, but will append the new value -; instead. - -; Note: path INI options can be relative and will be expanded with the prefix -; (pool, global or /usr/local) - -; Default Value: nothing is defined by default except the values in php.ini and -; specified at startup with the -d argument -;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com -;php_flag[display_errors] = off -;php_admin_value[error_log] = /var/log/fpm-php.www.log -;php_admin_flag[log_errors] = on -;php_admin_value[memory_limit] = 32M From 984330a475a1f93599fb114421531dc5909fcbe7 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 28 May 2024 10:55:26 +0200 Subject: [PATCH 339/514] UPDATE: updated the readme of the developement docker --- docker/development/README.md | 42 +++++++++++++----------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/docker/development/README.md b/docker/development/README.md index 1838becbc..46f3f9c95 100644 --- a/docker/development/README.md +++ b/docker/development/README.md @@ -1,35 +1,23 @@ -⚠ Caution: These instructions have not been updated since before version 1.0 of the project. Therefore, there may be inaccuracies, instability, or non-functional aspects. Proceed with care. (you may want to take a look at the standalone docker instread) +# 🐳 Docker Development Environment -# Building the development environment +This development environment utilizes standalone Docker with necessary tools pre-configured. -cd into the project directory and run the following command: `sh bin/startdocker.sh` -This should start building the images and start the containers. +## Included Services +- Redis +- MySQL +- Tools like phpMyAdmin -After that you need to go into the controlpanel_php container and run some commands: +## Not Included Services (for the moment) +- Pterodactyl +- Wings -Type `docker exec -it controlpanel_php ash` to go into the container and run the following commands: +Feel free to modify the Docker Compose file to remove any services you already have. -```shell -composer install -cp .env.example .env -php artisan key:generate --force -php artisan storage:link -php artisan migrate --seed --force -``` +## Setting Up the Testing Environment -## Setting up testing environment +1. **Manual Setup:** As mentioned in the standalone Docker guide, you will need to manually set up some components. +2. **Custom Configuration:** Modify the CtrlPanel Docker Compose configuration to use your current project as a base. If you point it to an empty folder, it will clone the repository, and you won't see your changes immediately. -Create the .env.testing file to your needs. Then once done you need to go into your phpmyadmin to create a new database named __controlpanel_test__. -Visit http://127.0.0.1:8080/ and create your database. +For detailed setup instructions, refer to the standalone Docker documentation. -Now you're ready to run the following commands which switches to the testing config, migrates the test database and seeds it. -After that you can switch back to your dev environment again. Clear the config from cache so changes will be instantly available. - -```shell -php artisan key:generate --force --env=testing -php artisan migrate:fresh --seed --env=testing -``` - -Now when running tests with PHPUnit it will use your testing database and not your local development one. -This is configured in the __phpunit.xml__. You can run your tests by running the command like this. Just type and enter. -`php artisan test`. +⚠ Caution: These instructions have not been finished. Therefore, there may be inaccuracies, instability, or non-functional aspects. Proceed with care. From 4e0370b051ecd82c895a93607bd69b23b4c41623 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 28 May 2024 21:55:01 +0200 Subject: [PATCH 340/514] Fix explode null parameter, Update dev docker compose file --- .../development/{docker-compose.yml => compose.yaml} | 11 ++++------- .../standalone/{docker-compose.yml => compose.yaml} | 0 2 files changed, 4 insertions(+), 7 deletions(-) rename docker/development/{docker-compose.yml => compose.yaml} (83%) rename docker/standalone/{docker-compose.yml => compose.yaml} (100%) diff --git a/docker/development/docker-compose.yml b/docker/development/compose.yaml similarity index 83% rename from docker/development/docker-compose.yml rename to docker/development/compose.yaml index 4d31230a7..e7169377e 100644 --- a/docker/development/docker-compose.yml +++ b/docker/development/compose.yaml @@ -1,8 +1,5 @@ -version: '3' - -// TODO: add wings and pterodactyl services: - + # TODO: add wings and pterodactyl controlpanel_standalone: build: context: ../../ @@ -13,8 +10,8 @@ services: - "80:80" - "443:443" volumes: - - './website_files:/var/www/html:rw' # change it to your project - - './nginx_config:/etc/nginx/conf.d/:rw' # change it + - '../..:/var/www/html:rw' + - './nginx_config:/etc/nginx/conf.d/:rw' networks: - laravel @@ -31,7 +28,7 @@ services: MYSQL_PASSWORD: root MYSQL_ROOT_PASSWORD: root volumes: - - "mysql:/var/lib/mysql:delegated" + - "./mysql:/var/lib/mysql:delegated" networks: - laravel diff --git a/docker/standalone/docker-compose.yml b/docker/standalone/compose.yaml similarity index 100% rename from docker/standalone/docker-compose.yml rename to docker/standalone/compose.yaml From 0596f7c78240e40de66b4f70dab6f539cb75347a Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 28 May 2024 22:03:37 +0200 Subject: [PATCH 341/514] Update gitignore, Fix explode null parameter --- .gitignore | 3 +++ config/trustedproxy.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ad9d095b8..aebb0fa86 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ storage/app/public/logo.png public/install/logs.txt install.lock public/install/logs/installer.log +public/install/logs/laravel.log +docker/development/nginx_config +docker/development/mysql diff --git a/config/trustedproxy.php b/config/trustedproxy.php index dc46c31ba..f3b88cd52 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -26,7 +26,7 @@ * subsequently passed through. */ 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? - env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', '')), /* * Or, to trust all proxies that connect From 78479a025d1621290412b90d18a7f52119c9b749 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 28 May 2024 22:43:11 +0200 Subject: [PATCH 342/514] Update dev compose.yml and gitignore --- .gitignore | 1 - docker/development/compose.yaml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index aebb0fa86..ee0e3762a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,5 @@ storage/app/public/logo.png public/install/logs.txt install.lock public/install/logs/installer.log -public/install/logs/laravel.log docker/development/nginx_config docker/development/mysql diff --git a/docker/development/compose.yaml b/docker/development/compose.yaml index e7169377e..3fd698db7 100644 --- a/docker/development/compose.yaml +++ b/docker/development/compose.yaml @@ -4,7 +4,7 @@ services: build: context: ../../ dockerfile: ./docker/standalone/Dockerfile - container_name: controlpanel_standalone + container_name: controlpanel_development restart: on-failure ports: - "80:80" From 299416daa3e37251ca816d5e4447041d86377f9e Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 28 May 2024 23:05:17 +0200 Subject: [PATCH 343/514] Revert explode deprecation fix, move gitignore to apropriate folder --- .gitignore | 2 -- config/trustedproxy.php | 2 +- docker/development/.gitignore | 3 +++ 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100755 docker/development/.gitignore diff --git a/.gitignore b/.gitignore index ee0e3762a..ad9d095b8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,3 @@ storage/app/public/logo.png public/install/logs.txt install.lock public/install/logs/installer.log -docker/development/nginx_config -docker/development/mysql diff --git a/config/trustedproxy.php b/config/trustedproxy.php index f3b88cd52..dc46c31ba 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -26,7 +26,7 @@ * subsequently passed through. */ 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? - env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', '')), + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), /* * Or, to trust all proxies that connect diff --git a/docker/development/.gitignore b/docker/development/.gitignore new file mode 100755 index 000000000..aaec72ce4 --- /dev/null +++ b/docker/development/.gitignore @@ -0,0 +1,3 @@ +!.gitignore +mysql +nginx_config From 3a7dc46d94a983d5ffab2acc067029e8e1f971c7 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 09:00:40 +0200 Subject: [PATCH 344/514] Add docker build workflow --- .github/workflows/docker-build.yml | 93 ++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .github/workflows/docker-build.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 000000000..166fc8abd --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,93 @@ +name: Build and Push Docker Images + +on: + push: + branches: + - main + - docker-github-workflow + tags: + - '*' + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + + +#name: Build and Push Docker Images +# +#on: +# push: +# branches: +# - main +# tags: +# - '*' +# +#env: +# REGISTRY: ghcr.io +# IMAGE_NAME: ${{ github.repository }} +# +#jobs: +# build-and-push-image: +# runs-on: ubuntu-latest +# permissions: +# contents: read +# packages: write +# +# steps: +# - name: Checkout repository +# uses: actions/checkout@v3 +# +# - name: Log in to the Container registry +# uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 +# with: +# registry: ${{ env.REGISTRY }} +# username: ${{ github.actor }} +# password: ${{ secrets.GITHUB_TOKEN }} +# +# - name: Extract metadata (tags, labels) for Docker +# id: meta +# uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 +# with: +# images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} +# +# - name: Build and push Docker image +# uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 +# with: +# context: . +# target: ${{ env.IMAGE_NAME }} +# push: true +# tags: ${{ steps.meta.outputs.tags }} +# labels: ${{ steps.meta.outputs.labels }} From aa5820cfe922c1a087e42591e2d3a0c6fc14bc53 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 09:02:38 +0200 Subject: [PATCH 345/514] Add Dockerfile location to github workflow --- .github/workflows/docker-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 166fc8abd..70d0c9c05 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -41,6 +41,7 @@ jobs: with: context: . push: true + file: docker/standalone/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} From 1968a977bf2f79044298da04d8d653d366f12315 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 09:22:53 +0200 Subject: [PATCH 346/514] Update standalone compose file --- .github/workflows/docker-build.yml | 55 +-------------------------- docker/development/compose.yaml | 12 +++--- docker/standalone/compose.yaml | 61 ++++++++++++++++++++++++++---- 3 files changed, 61 insertions(+), 67 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 70d0c9c05..54016c792 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,15 +1,12 @@ -name: Build and Push Docker Images +name: Build and Push Docker Image on: push: - branches: - - main - - docker-github-workflow tags: - '*' jobs: - build-and-push-image: + build-and-push-docker-image: runs-on: ubuntu-latest permissions: contents: read @@ -44,51 +41,3 @@ jobs: file: docker/standalone/Dockerfile tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - - - -#name: Build and Push Docker Images -# -#on: -# push: -# branches: -# - main -# tags: -# - '*' -# -#env: -# REGISTRY: ghcr.io -# IMAGE_NAME: ${{ github.repository }} -# -#jobs: -# build-and-push-image: -# runs-on: ubuntu-latest -# permissions: -# contents: read -# packages: write -# -# steps: -# - name: Checkout repository -# uses: actions/checkout@v3 -# -# - name: Log in to the Container registry -# uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 -# with: -# registry: ${{ env.REGISTRY }} -# username: ${{ github.actor }} -# password: ${{ secrets.GITHUB_TOKEN }} -# -# - name: Extract metadata (tags, labels) for Docker -# id: meta -# uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 -# with: -# images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} -# -# - name: Build and push Docker image -# uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 -# with: -# context: . -# target: ${{ env.IMAGE_NAME }} -# push: true -# tags: ${{ steps.meta.outputs.tags }} -# labels: ${{ steps.meta.outputs.labels }} diff --git a/docker/development/compose.yaml b/docker/development/compose.yaml index 3fd698db7..c3010c59f 100644 --- a/docker/development/compose.yaml +++ b/docker/development/compose.yaml @@ -1,6 +1,6 @@ services: # TODO: add wings and pterodactyl - controlpanel_standalone: + controlpanel_panel: build: context: ../../ dockerfile: ./docker/standalone/Dockerfile @@ -13,7 +13,7 @@ services: - '../..:/var/www/html:rw' - './nginx_config:/etc/nginx/conf.d/:rw' networks: - - laravel + - controlpanel mysql: image: mysql @@ -30,7 +30,7 @@ services: volumes: - "./mysql:/var/lib/mysql:delegated" networks: - - laravel + - controlpanel phpmyadmin: image: phpmyadmin/phpmyadmin @@ -45,7 +45,7 @@ services: - PMA_PASSWORD=root - PMA_ARBITRARY=1 networks: - - laravel + - controlpanel redis: image: redis @@ -54,7 +54,7 @@ services: ports: - "6379:6379" networks: - - laravel + - controlpanel networks: - laravel: + controlpanel: diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index cd980a2af..11b3dd85d 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -1,15 +1,60 @@ -version: '3' - services: - controlpanel_standalone: - build: - context: ../../ - dockerfile: ./docker/standalone/Dockerfile - container_name: controlpanel_standalone - restart: on-failure + controlpanel: + image: ghcr.io/jameskitt616/panel:latest + container_name: controlpanel_panel + restart: unless-stopped + depends_on: + - redis ports: - "80:80" - "443:443" volumes: - './website_files:/var/www/html:rw' # change it - './nginx_config:/etc/nginx/conf.d/:rw' # change it + networks: + - controlpanel + + mysql: + image: mysql + container_name: controlpanel_mysql + restart: unless-stopped + tty: true + ports: + - "3306:3306" + environment: + MYSQL_DATABASE: controlpanel + MYSQL_USER: controlpaneluser + MYSQL_PASSWORD: root # change it + MYSQL_ROOT_PASSWORD: root # change it + volumes: + - "./mysql:/var/lib/mysql:delegated" + networks: + - controlpanel + + phpmyadmin: + image: phpmyadmin/phpmyadmin + container_name: controlpanel_phpmyadmin + restart: unless-stopped + depends_on: + - mysql + ports: + - '8080:80' + environment: + - PMA_HOST=controlpanel_mysql + - PMA_USER=root # change it + - PMA_PASSWORD=root # change it + - PMA_ARBITRARY=1 + networks: + - controlpanel + + redis: + image: redis + container_name: controlpanel_redis + restart: unless-stopped + ports: + - "6379:6379" + networks: + - controlpanel + +networks: + controlpanel: From 93fb0b7d5e523900aa25a4a1710d228dd676cffa Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 09:28:06 +0200 Subject: [PATCH 347/514] Change repository owner name --- docker/standalone/compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index 11b3dd85d..ba129fa4d 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -1,6 +1,6 @@ services: controlpanel: - image: ghcr.io/jameskitt616/panel:latest + image: ghcr.io/ctrlpanel-gg/panel:latest container_name: controlpanel_panel restart: unless-stopped depends_on: From 9fef8ab964a22bbd55e0e7e63abe96ef5980c11f Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 09:40:44 +0200 Subject: [PATCH 348/514] Update docker readme --- docker/standalone/README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docker/standalone/README.md b/docker/standalone/README.md index 992b80c82..489c079d5 100644 --- a/docker/standalone/README.md +++ b/docker/standalone/README.md @@ -7,8 +7,15 @@ If you're using a different operating system, you can follow the official Docker Once you have Docker installed, you can run CtrlPanel standalone Docker by executing the following command: +Recommended way via Docker Compose: + +Get the Compose file [here](https://github.com/Ctrlpanel-gg/panel/blob/docker-github-workflow/docker/standalone/compose.yaml). +This also includes all necessaries like a Database, Redis and optionally phpmyadmin to manage the Database. + +Running as commandline command: + ```bash -docker run -p 80:80 -p 443:443 -v /path/to/website_files:/var/www/html -v /path/to/nginx_config:/etc/nginx/conf.d/ ctrlpanel/ctrlpanel +docker run -p 80:80 -p 443:443 -v /path/to/website_files:/var/www/html -v /path/to/nginx_config:/etc/nginx/conf.d/ ghcr.io/ctrlpanel-gg/panel:latest ``` This command will run the latest CtrlPanel Docker image from Docker Hub and run it. From 7a40c3837035af57e808bb99b02b83961e029e95 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 09:46:50 +0200 Subject: [PATCH 349/514] Update docker readme with redis instructions --- docker/standalone/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/standalone/README.md b/docker/standalone/README.md index 489c079d5..200234bf6 100644 --- a/docker/standalone/README.md +++ b/docker/standalone/README.md @@ -18,6 +18,8 @@ Running as commandline command: docker run -p 80:80 -p 443:443 -v /path/to/website_files:/var/www/html -v /path/to/nginx_config:/etc/nginx/conf.d/ ghcr.io/ctrlpanel-gg/panel:latest ``` +When installing you need to update the `.env` file. Change those two variables to: `MEMCACHED_HOST=redis` and `REDIS_HOST=redis`, to use the Redis server which comes with the docker compose installation. + This command will run the latest CtrlPanel Docker image from Docker Hub and run it. The control panel will be available at http://localhost/install and will be a completely fresh installation. From 86de9ecc4cf4f177e4edb05e6917ecff202c640c Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 12:56:20 +0200 Subject: [PATCH 350/514] Update startup script --- config/trustedproxy.php | 2 +- docker/development/compose.yaml | 4 +--- docker/standalone/compose.yaml | 10 ++++++---- docker/standalone/scripts/startup.sh | 1 + 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/config/trustedproxy.php b/config/trustedproxy.php index dc46c31ba..f3b88cd52 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -26,7 +26,7 @@ * subsequently passed through. */ 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? - env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', '')), /* * Or, to trust all proxies that connect diff --git a/docker/development/compose.yaml b/docker/development/compose.yaml index c3010c59f..0c9e49ff5 100644 --- a/docker/development/compose.yaml +++ b/docker/development/compose.yaml @@ -5,15 +5,13 @@ services: context: ../../ dockerfile: ./docker/standalone/Dockerfile container_name: controlpanel_development - restart: on-failure + restart: unless-stopped ports: - "80:80" - "443:443" volumes: - '../..:/var/www/html:rw' - './nginx_config:/etc/nginx/conf.d/:rw' - networks: - - controlpanel mysql: image: mysql diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index ba129fa4d..51d9a9959 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -9,10 +9,12 @@ services: - "80:80" - "443:443" volumes: - - './website_files:/var/www/html:rw' # change it - - './nginx_config:/etc/nginx/conf.d/:rw' # change it - networks: - - controlpanel + - './env:/var/www/html/.env:rw' + - './logs:/var/www/html/storage/logs:rw' +# - './website_files:/var/www/html:rw' # change it +# - './nginx_config:/etc/nginx/conf.d/:rw' # change it +# networks: +# - controlpanel mysql: image: mysql diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 5f36512d9..17a36203f 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -32,6 +32,7 @@ if [ -z "$(ls -A /var/www/html)" ]; then cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files chown -R laravel:laravel /var/www/html/ chmod -R 755 /var/www/html + chmod -R 644 $LOG_DIR fi # Check and copy default Nginx configuration if not exists From 47cdd8e17c2739e78fc06944d0fe2abd2de49200 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 13:04:10 +0200 Subject: [PATCH 351/514] Add network to dev compose --- docker/development/compose.yaml | 2 ++ docker/standalone/compose.yaml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/development/compose.yaml b/docker/development/compose.yaml index 0c9e49ff5..ef2c3c2e1 100644 --- a/docker/development/compose.yaml +++ b/docker/development/compose.yaml @@ -12,6 +12,8 @@ services: volumes: - '../..:/var/www/html:rw' - './nginx_config:/etc/nginx/conf.d/:rw' + networks: + - controlpanel mysql: image: mysql diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index 51d9a9959..960b14bdb 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -13,8 +13,8 @@ services: - './logs:/var/www/html/storage/logs:rw' # - './website_files:/var/www/html:rw' # change it # - './nginx_config:/etc/nginx/conf.d/:rw' # change it -# networks: -# - controlpanel + networks: + - controlpanel mysql: image: mysql From 956e6793742bb40ad0f6264d5a8e68821ec39ebc Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 13:08:53 +0200 Subject: [PATCH 352/514] Set database name and user to defaults from installer --- docker/development/compose.yaml | 4 ++-- docker/standalone/compose.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/development/compose.yaml b/docker/development/compose.yaml index ef2c3c2e1..7f5acb0f8 100644 --- a/docker/development/compose.yaml +++ b/docker/development/compose.yaml @@ -23,8 +23,8 @@ services: ports: - "3306:3306" environment: - MYSQL_DATABASE: controlpanel - MYSQL_USER: controlpanel + MYSQL_DATABASE: ctrlpanel + MYSQL_USER: ctrlpaneluser MYSQL_PASSWORD: root MYSQL_ROOT_PASSWORD: root volumes: diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index 960b14bdb..0551eb61d 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -24,8 +24,8 @@ services: ports: - "3306:3306" environment: - MYSQL_DATABASE: controlpanel - MYSQL_USER: controlpaneluser + MYSQL_DATABASE: ctrlpanel + MYSQL_USER: ctrlpaneluser MYSQL_PASSWORD: root # change it MYSQL_ROOT_PASSWORD: root # change it volumes: From e054cb9a33036e418ce5d36d062647071ae336d1 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 13:29:30 +0200 Subject: [PATCH 353/514] Update permissions --- docker/standalone/scripts/startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 17a36203f..39092d919 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -32,7 +32,7 @@ if [ -z "$(ls -A /var/www/html)" ]; then cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files chown -R laravel:laravel /var/www/html/ chmod -R 755 /var/www/html - chmod -R 644 $LOG_DIR + chmod -R 664 $LOG_DIR fi # Check and copy default Nginx configuration if not exists From b3862a48e03511dfc3cf9c98dcd700bfdf418980 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 14:07:05 +0200 Subject: [PATCH 354/514] Try to determine if process is running within a docker container --- public/install/functions.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/install/functions.php b/public/install/functions.php index a5f2b0454..046ea1a6f 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -236,14 +236,18 @@ function run_console(string $command, array $descriptors = null, string $cwd = n $path = dirname(__FILE__, 3); $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); + if (file_exists('/.dockerenv')) { + $handle = proc_open("cd '$path' && bash -c '$command'", $descriptors, $pipes, $cwd, null, $options); + } else { + $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); + } + $output = stream_get_contents($pipes[1]); $exit_code = proc_close($handle); if ($exit_code > 0) { wh_log('command result: ' . $output, 'error'); throw new Exception("There was an error after running command `$command`", $exit_code); - return $output; } else { return $output; } From aadd164d55255b342894d9db99501ae7a71b2150 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 14:27:16 +0200 Subject: [PATCH 355/514] Force conecole command without exec -a --- public/install/functions.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/install/functions.php b/public/install/functions.php index 046ea1a6f..51b4c629e 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -236,11 +236,11 @@ function run_console(string $command, array $descriptors = null, string $cwd = n $path = dirname(__FILE__, 3); $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - if (file_exists('/.dockerenv')) { +// if (file_exists('/.dockerenv')) { $handle = proc_open("cd '$path' && bash -c '$command'", $descriptors, $pipes, $cwd, null, $options); - } else { - $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); - } +// } else { +// $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); +// } $output = stream_get_contents($pipes[1]); $exit_code = proc_close($handle); From e564a3886062ba9d86312d05c7f8ec19dbf94aca Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 14:44:01 +0200 Subject: [PATCH 356/514] Comment out everything in run_console command --- public/install/functions.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/public/install/functions.php b/public/install/functions.php index 51b4c629e..5a964bf3e 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -235,15 +235,20 @@ function run_console(string $command, array $descriptors = null, string $cwd = n wh_log('running command: ' . $command, 'debug'); $path = dirname(__FILE__, 3); - $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + wh_log('running command: ' . "cd '$path' && bash -c '$command'", 'debug'); + +// $path = dirname(__FILE__, 3); +// $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; // if (file_exists('/.dockerenv')) { - $handle = proc_open("cd '$path' && bash -c '$command'", $descriptors, $pipes, $cwd, null, $options); +// $handle = proc_open("cd '$path' && bash -c '$command'", $descriptors, $pipes, $cwd, null, $options); // } else { // $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); // } - $output = stream_get_contents($pipes[1]); - $exit_code = proc_close($handle); +// $output = stream_get_contents($pipes[1]); +// $exit_code = proc_close($handle); + $exit_code = 0; + $output = 'yippie'; if ($exit_code > 0) { wh_log('command result: ' . $output, 'error'); From b8d6d59d14179abd5a616f3959b5bff6226d39e4 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 14:58:16 +0200 Subject: [PATCH 357/514] Set different permissions --- config/logging.php | 6 +++--- public/install/functions.php | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/config/logging.php b/config/logging.php index 5d4134ffa..a09c721f7 100644 --- a/config/logging.php +++ b/config/logging.php @@ -61,7 +61,7 @@ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), - 'permission' => 0664, + 'permission' => 0644, ], 'daily' => [ @@ -69,7 +69,7 @@ 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, - 'permission' => 0664, + 'permission' => 0644, ], 'slack' => [ @@ -118,7 +118,7 @@ 'emergency' => [ 'path' => storage_path('logs/laravel.log'), - 'permission' => 0664, + 'permission' => 0644, ], ], diff --git a/public/install/functions.php b/public/install/functions.php index 5a964bf3e..519a88414 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -237,18 +237,17 @@ function run_console(string $command, array $descriptors = null, string $cwd = n $path = dirname(__FILE__, 3); wh_log('running command: ' . "cd '$path' && bash -c '$command'", 'debug'); -// $path = dirname(__FILE__, 3); -// $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $path = dirname(__FILE__, 3); + $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; // if (file_exists('/.dockerenv')) { +// if (!file_exists('../../.env')) { // $handle = proc_open("cd '$path' && bash -c '$command'", $descriptors, $pipes, $cwd, null, $options); // } else { -// $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); + $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); // } -// $output = stream_get_contents($pipes[1]); -// $exit_code = proc_close($handle); - $exit_code = 0; - $output = 'yippie'; + $output = stream_get_contents($pipes[1]); + $exit_code = proc_close($handle); if ($exit_code > 0) { wh_log('command result: ' . $output, 'error'); From c5ad334b2bea7ed5bb466f56994c5729bf8f52b0 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 14:59:26 +0200 Subject: [PATCH 358/514] Set different permissions --- docker/standalone/scripts/startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 39092d919..17a36203f 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -32,7 +32,7 @@ if [ -z "$(ls -A /var/www/html)" ]; then cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files chown -R laravel:laravel /var/www/html/ chmod -R 755 /var/www/html - chmod -R 664 $LOG_DIR + chmod -R 644 $LOG_DIR fi # Check and copy default Nginx configuration if not exists From 92c16559ddc7905d9c1897ae2fa613687b20f3bc Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 15:12:20 +0200 Subject: [PATCH 359/514] Open up permissions --- config/logging.php | 6 +++--- docker/standalone/scripts/startup.sh | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config/logging.php b/config/logging.php index a09c721f7..dc1ce25da 100644 --- a/config/logging.php +++ b/config/logging.php @@ -61,7 +61,7 @@ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), - 'permission' => 0644, + 'permission' => 0777, ], 'daily' => [ @@ -69,7 +69,7 @@ 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, - 'permission' => 0644, + 'permission' => 0777, ], 'slack' => [ @@ -118,7 +118,7 @@ 'emergency' => [ 'path' => storage_path('logs/laravel.log'), - 'permission' => 0644, + 'permission' => 0777, ], ], diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 17a36203f..144350ef5 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -32,9 +32,10 @@ if [ -z "$(ls -A /var/www/html)" ]; then cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files chown -R laravel:laravel /var/www/html/ chmod -R 755 /var/www/html - chmod -R 644 $LOG_DIR fi +chmod -R 777 $LOG_DIR + # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then log_message "Warning: Nginx configuration not found. Copying default configuration..." From fec23eec1f1fbe5f9e9268bb40c0ef78d074d351 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 15:26:25 +0200 Subject: [PATCH 360/514] Revert stuff in functions.php --- docker/standalone/scripts/startup.sh | 3 ++- public/install/functions.php | 10 +--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 144350ef5..b4ab19552 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -34,7 +34,8 @@ if [ -z "$(ls -A /var/www/html)" ]; then chmod -R 755 /var/www/html fi -chmod -R 777 $LOG_DIR +#chmod -R 755 /var/www/html +#chmod -R 777 $LOG_DIR # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then diff --git a/public/install/functions.php b/public/install/functions.php index 519a88414..7b482732f 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -234,17 +234,9 @@ function run_console(string $command, array $descriptors = null, string $cwd = n { wh_log('running command: ' . $command, 'debug'); - $path = dirname(__FILE__, 3); - wh_log('running command: ' . "cd '$path' && bash -c '$command'", 'debug'); - $path = dirname(__FILE__, 3); $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; -// if (file_exists('/.dockerenv')) { -// if (!file_exists('../../.env')) { -// $handle = proc_open("cd '$path' && bash -c '$command'", $descriptors, $pipes, $cwd, null, $options); -// } else { - $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); -// } + $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); $output = stream_get_contents($pipes[1]); $exit_code = proc_close($handle); From 786bac37ffa79c30a3dbc01a605364facd5aafc9 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 15:38:47 +0200 Subject: [PATCH 361/514] Reset permissions --- config/logging.php | 6 +++--- public/install/functions.php | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/config/logging.php b/config/logging.php index dc1ce25da..5d4134ffa 100644 --- a/config/logging.php +++ b/config/logging.php @@ -61,7 +61,7 @@ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), - 'permission' => 0777, + 'permission' => 0664, ], 'daily' => [ @@ -69,7 +69,7 @@ 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, - 'permission' => 0777, + 'permission' => 0664, ], 'slack' => [ @@ -118,7 +118,7 @@ 'emergency' => [ 'path' => storage_path('logs/laravel.log'), - 'permission' => 0777, + 'permission' => 0664, ], ], diff --git a/public/install/functions.php b/public/install/functions.php index 7b482732f..912a90180 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -237,7 +237,6 @@ function run_console(string $command, array $descriptors = null, string $cwd = n $path = dirname(__FILE__, 3); $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); - $output = stream_get_contents($pipes[1]); $exit_code = proc_close($handle); From a2e0109e9df3c00882dadcd3c73a27b086d5011c Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 15:42:52 +0200 Subject: [PATCH 362/514] Update permissions in startup.sh --- docker/standalone/scripts/startup.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index b4ab19552..dc1585995 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -30,11 +30,11 @@ if [ -z "$(ls -A /var/www/html)" ]; then log_message "Warning: project folder is empty. Copying default files..." # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files - chown -R laravel:laravel /var/www/html/ - chmod -R 755 /var/www/html +# chown -R laravel:laravel /var/www/html/ +# chmod -R 755 /var/www/html fi -#chmod -R 755 /var/www/html +chmod -R 755 /var/www/html #chmod -R 777 $LOG_DIR # Check and copy default Nginx configuration if not exists From 76463c940bb787d8ef3473e0d533724ac02944fe Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 15:55:45 +0200 Subject: [PATCH 363/514] Add chown to startup.sh --- docker/standalone/scripts/startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index dc1585995..b44b959dc 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -34,8 +34,8 @@ if [ -z "$(ls -A /var/www/html)" ]; then # chmod -R 755 /var/www/html fi +chown -R laravel:laravel /var/www/html/ chmod -R 755 /var/www/html -#chmod -R 777 $LOG_DIR # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then From dffcb25afe746fe9f24ee03456a32db138f3eeae Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 16:21:05 +0200 Subject: [PATCH 364/514] Add permissions to streamhandler --- docker/standalone/Dockerfile | 3 +++ docker/standalone/compose.yaml | 6 +++--- docker/standalone/scripts/startup.sh | 8 ++++---- public/install/functions.php | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index a16bed8cb..9fd5e4770 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -58,5 +58,8 @@ RUN chmod +x /usr/local/bin/startup-script.sh # Set the working directory WORKDIR /var/www/html +#RUN chmod -R 755 /var/www/html +#RUN chown -R laravel:laravel /var/www/html + # Start the startup script CMD ["/usr/local/bin/startup-script.sh"] diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index 0551eb61d..6b00f27df 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -9,9 +9,9 @@ services: - "80:80" - "443:443" volumes: - - './env:/var/www/html/.env:rw' - - './logs:/var/www/html/storage/logs:rw' -# - './website_files:/var/www/html:rw' # change it +# - './env:/var/www/html/.env:rw' +# - './logs:/var/www/html/storage/logs:rw' + - './website_files:/var/www/html:rw' # change it # - './nginx_config:/etc/nginx/conf.d/:rw' # change it networks: - controlpanel diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index b44b959dc..3bfa5f212 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -30,12 +30,12 @@ if [ -z "$(ls -A /var/www/html)" ]; then log_message "Warning: project folder is empty. Copying default files..." # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files -# chown -R laravel:laravel /var/www/html/ -# chmod -R 755 /var/www/html + chown -R laravel:laravel /var/www/html/ + chmod -R 755 /var/www/html/ fi -chown -R laravel:laravel /var/www/html/ -chmod -R 755 /var/www/html +#chown -R laravel:laravel /var/www/html/ +#chmod -R 755 /var/www/html/ # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then diff --git a/public/install/functions.php b/public/install/functions.php index 912a90180..e44d2f3c1 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -258,7 +258,7 @@ function run_console(string $command, array $descriptors = null, string $cwd = n function wh_log(string $message, string $level = 'info', array $context = []): void { $formatter = new LineFormatter(null, null, true, true); - $stream = new StreamHandler(dirname(__FILE__, 3) . '/storage/logs/installer.log', Logger::DEBUG); + $stream = new StreamHandler(dirname(__FILE__, 3) . '/storage/logs/installer.log', Logger::DEBUG, true, 664); $stream->setFormatter($formatter); $log = new Logger('ControlPanel'); From 0a3cc259640ed1156553d3e3846b631ee0ef7c9a Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 16:41:16 +0200 Subject: [PATCH 365/514] Revert StreamHandler permissions --- docker/standalone/Dockerfile | 3 --- docker/standalone/scripts/startup.sh | 4 ++-- public/install/functions.php | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index 9fd5e4770..a16bed8cb 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -58,8 +58,5 @@ RUN chmod +x /usr/local/bin/startup-script.sh # Set the working directory WORKDIR /var/www/html -#RUN chmod -R 755 /var/www/html -#RUN chown -R laravel:laravel /var/www/html - # Start the startup script CMD ["/usr/local/bin/startup-script.sh"] diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 3bfa5f212..57e7195da 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -34,8 +34,8 @@ if [ -z "$(ls -A /var/www/html)" ]; then chmod -R 755 /var/www/html/ fi -#chown -R laravel:laravel /var/www/html/ -#chmod -R 755 /var/www/html/ +chown -R laravel:laravel /var/www/html/ +chmod -R 755 /var/www/html/ # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then diff --git a/public/install/functions.php b/public/install/functions.php index e44d2f3c1..912a90180 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -258,7 +258,7 @@ function run_console(string $command, array $descriptors = null, string $cwd = n function wh_log(string $message, string $level = 'info', array $context = []): void { $formatter = new LineFormatter(null, null, true, true); - $stream = new StreamHandler(dirname(__FILE__, 3) . '/storage/logs/installer.log', Logger::DEBUG, true, 664); + $stream = new StreamHandler(dirname(__FILE__, 3) . '/storage/logs/installer.log', Logger::DEBUG); $stream->setFormatter($formatter); $log = new Logger('ControlPanel'); From d6a01b66f1c8d9453be7cd888d5f85decfe945d3 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 16:41:44 +0200 Subject: [PATCH 366/514] Revert trustedpries --- config/trustedproxy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/trustedproxy.php b/config/trustedproxy.php index f3b88cd52..dc46c31ba 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -26,7 +26,7 @@ * subsequently passed through. */ 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? - env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', '')), + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), /* * Or, to trust all proxies that connect From f523ea6f2b09035682853b6eb103c5b641f73fc4 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 30 May 2024 17:05:36 +0200 Subject: [PATCH 367/514] Revert permissions in startup.sh --- docker/standalone/scripts/startup.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 57e7195da..5f36512d9 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -31,12 +31,9 @@ if [ -z "$(ls -A /var/www/html)" ]; then # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files chown -R laravel:laravel /var/www/html/ - chmod -R 755 /var/www/html/ + chmod -R 755 /var/www/html fi -chown -R laravel:laravel /var/www/html/ -chmod -R 755 /var/www/html/ - # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then log_message "Warning: Nginx configuration not found. Copying default configuration..." From c285f8f0944d3d1c6e46e1f5f5eb382b08c5de5a Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 1 Jun 2024 09:47:05 +0200 Subject: [PATCH 368/514] Copy .env file in Dockerfile --- docker/standalone/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index a16bed8cb..5880acee2 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -50,6 +50,9 @@ RUN mkdir -p /var/log/nginx && chown -R laravel:laravel /var/log/nginx # Expose ports EXPOSE 80 443 +# Copy .env file for it to be availbe when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) +COPY --chown=laravel:laravel ./.env.example /var/www/html/.env + # Copy startup script COPY --chown=laravel:laravel ./docker/standalone/scripts/startup.sh /usr/local/bin/startup-script.sh # Make startup script executable @@ -58,5 +61,6 @@ RUN chmod +x /usr/local/bin/startup-script.sh # Set the working directory WORKDIR /var/www/html + # Start the startup script CMD ["/usr/local/bin/startup-script.sh"] From bdffb660ccdf044b13947ce9876c8de97ce12b10 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 1 Jun 2024 10:22:25 +0200 Subject: [PATCH 369/514] copy env file in startup script --- docker/standalone/Dockerfile | 5 ++--- docker/standalone/scripts/startup.sh | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index 5880acee2..474b7a98e 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -50,8 +50,8 @@ RUN mkdir -p /var/log/nginx && chown -R laravel:laravel /var/log/nginx # Expose ports EXPOSE 80 443 -# Copy .env file for it to be availbe when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) -COPY --chown=laravel:laravel ./.env.example /var/www/html/.env +# Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) +#COPY --chown=laravel:laravel /var/default/.env.example /var/www/html/.env # Copy startup script COPY --chown=laravel:laravel ./docker/standalone/scripts/startup.sh /usr/local/bin/startup-script.sh @@ -61,6 +61,5 @@ RUN chmod +x /usr/local/bin/startup-script.sh # Set the working directory WORKDIR /var/www/html - # Start the startup script CMD ["/usr/local/bin/startup-script.sh"] diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 5f36512d9..fd70d3151 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -34,6 +34,9 @@ if [ -z "$(ls -A /var/www/html)" ]; then chmod -R 755 /var/www/html fi +# Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) +cp -nr /var/www/html/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files + # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then log_message "Warning: Nginx configuration not found. Copying default configuration..." From d6d5fd878b689898e46aa9343bd523858ae33912 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 1 Jun 2024 10:26:29 +0200 Subject: [PATCH 370/514] Copy .env.example from different dir --- docker/standalone/scripts/startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index fd70d3151..ee90e2152 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -35,7 +35,7 @@ if [ -z "$(ls -A /var/www/html)" ]; then fi # Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) -cp -nr /var/www/html/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files +cp -nr /var/default/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then From 318781f95426dc718f58940b9d464fb55f0719d8 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 2 Jun 2024 08:45:34 +0200 Subject: [PATCH 371/514] Remove reverse parameter from copy single file --- docker/standalone/scripts/startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index ee90e2152..b90814719 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -35,7 +35,7 @@ if [ -z "$(ls -A /var/www/html)" ]; then fi # Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) -cp -nr /var/default/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files +cp -n /var/default/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files # Check and copy default Nginx configuration if not exists if [ ! -f "/etc/nginx/conf.d/default.conf" ]; then From bba697b713eb218d4b8477e91c298fc5978d8acd Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:17:32 +0200 Subject: [PATCH 372/514] Create dir on startup script --- docker/standalone/scripts/startup.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index b90814719..c67494bdc 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -25,6 +25,8 @@ log_message() { echo "$1" } +mkdir /var/www/html/ + # Check if project folder is empty. if [ -z "$(ls -A /var/www/html)" ]; then log_message "Warning: project folder is empty. Copying default files..." From 9cffe584b0408f3fbfc4704b2e5dfb2d849f805f Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:23:09 +0200 Subject: [PATCH 373/514] Run copy of project files every time --- docker/standalone/scripts/startup.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index c67494bdc..37af86690 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -25,16 +25,14 @@ log_message() { echo "$1" } -mkdir /var/www/html/ - # Check if project folder is empty. -if [ -z "$(ls -A /var/www/html)" ]; then +#if [ -z "$(ls -A /var/www/html)" ]; then log_message "Warning: project folder is empty. Copying default files..." # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files chown -R laravel:laravel /var/www/html/ chmod -R 755 /var/www/html -fi +#fi # Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) cp -n /var/default/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files From a51ff578f43d99cb7ce8cf5c20f725fda8779175 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:34:27 +0200 Subject: [PATCH 374/514] ls project dirs to check if they are empty --- docker/standalone/scripts/startup.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 37af86690..1055d685a 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -25,14 +25,20 @@ log_message() { echo "$1" } +log_message "Contents of /var/default:" +ls -l /var/default + # Check if project folder is empty. -#if [ -z "$(ls -A /var/www/html)" ]; then +if [ -z "$(ls -A /var/www/html)" ]; then log_message "Warning: project folder is empty. Copying default files..." # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files chown -R laravel:laravel /var/www/html/ chmod -R 755 /var/www/html -#fi +fi + +log_message "Contents of /var/www/html:" +ls -l /var/www/html # Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) cp -n /var/default/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files From 7c3d70285f0b3c2a0255e58ce2c542df0b761443 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:43:10 +0200 Subject: [PATCH 375/514] Show and set permissions for project folder --- docker/standalone/scripts/startup.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 1055d685a..99380da14 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -26,19 +26,24 @@ log_message() { } log_message "Contents of /var/default:" -ls -l /var/default +ls -la /var/default + +log_message "Permissions of /var/www/html:" +ls -la /var/www/html # Check if project folder is empty. if [ -z "$(ls -A /var/www/html)" ]; then + chown -R laravel:laravel /var/www/html/ + chmod -R 777 /var/www/html/ log_message "Warning: project folder is empty. Copying default files..." # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files chown -R laravel:laravel /var/www/html/ - chmod -R 755 /var/www/html + chmod -R 755 /var/www/html/ fi log_message "Contents of /var/www/html:" -ls -l /var/www/html +ls -la /var/www/html # Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) cp -n /var/default/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files From 4c833e2f5cf590c2722d54447225109b05e6b6ce Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 2 Jun 2024 10:31:39 +0200 Subject: [PATCH 376/514] Run copy project files anyways --- docker/standalone/scripts/startup.sh | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 99380da14..e909ebcce 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -31,16 +31,24 @@ ls -la /var/default log_message "Permissions of /var/www/html:" ls -la /var/www/html +chown -R laravel:laravel /var/www/html/ +chmod -R 777 /var/www/html/ +log_message "Warning: project folder is empty. Copying default files..." +# Copy everything from /var/default to /var/www/html +cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files +chown -R laravel:laravel /var/www/html/ +chmod -R 755 /var/www/html/ + # Check if project folder is empty. -if [ -z "$(ls -A /var/www/html)" ]; then - chown -R laravel:laravel /var/www/html/ - chmod -R 777 /var/www/html/ - log_message "Warning: project folder is empty. Copying default files..." - # Copy everything from /var/default to /var/www/html - cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files - chown -R laravel:laravel /var/www/html/ - chmod -R 755 /var/www/html/ -fi +#if [ -z "$(ls -A /var/www/html)" ]; then +# chown -R laravel:laravel /var/www/html/ +# chmod -R 777 /var/www/html/ +# log_message "Warning: project folder is empty. Copying default files..." +# # Copy everything from /var/default to /var/www/html +# cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files +# chown -R laravel:laravel /var/www/html/ +# chmod -R 755 /var/www/html/ +#fi log_message "Contents of /var/www/html:" ls -la /var/www/html From 29665709590283f6dd281309b70762181fd2e1d1 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Tue, 4 Jun 2024 15:23:32 +0200 Subject: [PATCH 377/514] docs: precised how PR should be created in contributing.md --- .github/CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c00a9e604..93b4ee75c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -16,7 +16,7 @@ Please read our [Localization Guide](https://github.com/Ctrlpanel-gg/panel/blob/ ## 🚀 Pull Request Process -1. Provide a clear and descriptive title for your pull request (PR) summarizing the changes. +1. Provide a clear and descriptive title for your pull request (PR) summarizing the changes in this format : 'commit-norms-action: what-you-are-doing'. 2. If your PR is not yet finished, correctly mark it as a draft and mention any errors it's correcting. 3. The development team will review your code and offer feedback or approve/merge it as necessary. 4. Ensure that your PR adheres to our Code of Conduct and coding style guidelines. @@ -29,7 +29,7 @@ Please read our [Localization Guide](https://github.com/Ctrlpanel-gg/panel/blob/ We adhere to the PSR12 code standard for PHP. - Follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) commit norms. -- Name your PR branch as [action]/what-you-are-doing. +- Name your PR branch as [commit-norms-action]/what-you-are-doing. - Make clear commits, one per action, and include comments. ⚠️ **Important Note:** The owner of the project has the final decision, and the development team of CtrlPanel reserves the right to close incorrect PRs. PRs that remain inactive or invalid for an extended period may also be subject to closure. From 863299b4c08e17292aa91d179a8162757bba61f5 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:23:41 +0200 Subject: [PATCH 378/514] Attempt to fix startup script --- docker/standalone/README.md | 7 +++-- docker/standalone/scripts/startup.sh | 38 +++++++++------------------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/docker/standalone/README.md b/docker/standalone/README.md index 200234bf6..5a11a24c2 100644 --- a/docker/standalone/README.md +++ b/docker/standalone/README.md @@ -9,8 +9,9 @@ Once you have Docker installed, you can run CtrlPanel standalone Docker by execu Recommended way via Docker Compose: -Get the Compose file [here](https://github.com/Ctrlpanel-gg/panel/blob/docker-github-workflow/docker/standalone/compose.yaml). -This also includes all necessaries like a Database, Redis and optionally phpmyadmin to manage the Database. +1. Copy and configure your docker compose file to your needs `curl -L -o docker-compose.yml https://raw.githubusercontent.com/Ctrlpanel-gg/panel/blob/main/docker/standalone/compose.yaml`. +2. Create the env file in the same directory as the compose file `touch env_file`. +3. When installing you need to update the `env_file` file. Change those two variables to: `MEMCACHED_HOST=redis` and `REDIS_HOST=redis`, to use the Redis server which comes with the docker compose installation. Running as commandline command: @@ -18,8 +19,6 @@ Running as commandline command: docker run -p 80:80 -p 443:443 -v /path/to/website_files:/var/www/html -v /path/to/nginx_config:/etc/nginx/conf.d/ ghcr.io/ctrlpanel-gg/panel:latest ``` -When installing you need to update the `.env` file. Change those two variables to: `MEMCACHED_HOST=redis` and `REDIS_HOST=redis`, to use the Redis server which comes with the docker compose installation. - This command will run the latest CtrlPanel Docker image from Docker Hub and run it. The control panel will be available at http://localhost/install and will be a completely fresh installation. diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index e909ebcce..51e1eb328 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -25,35 +25,21 @@ log_message() { echo "$1" } -log_message "Contents of /var/default:" -ls -la /var/default - -log_message "Permissions of /var/www/html:" -ls -la /var/www/html - -chown -R laravel:laravel /var/www/html/ -chmod -R 777 /var/www/html/ -log_message "Warning: project folder is empty. Copying default files..." -# Copy everything from /var/default to /var/www/html -cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files -chown -R laravel:laravel /var/www/html/ -chmod -R 755 /var/www/html/ +# The issue seems that "index.nginx-debian.html" seems to be in the folder, hence the next check will always fail. +rm /var/www/html/index.nginx-debian.html # Check if project folder is empty. -#if [ -z "$(ls -A /var/www/html)" ]; then -# chown -R laravel:laravel /var/www/html/ -# chmod -R 777 /var/www/html/ -# log_message "Warning: project folder is empty. Copying default files..." -# # Copy everything from /var/default to /var/www/html -# cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files -# chown -R laravel:laravel /var/www/html/ -# chmod -R 755 /var/www/html/ -#fi - -log_message "Contents of /var/www/html:" -ls -la /var/www/html +if [ -z "$(ls -A /var/www/html)" ]; then + chown -R laravel:laravel /var/www/html/ + chmod -R 777 /var/www/html/ + log_message "Warning: project folder is empty. Copying default files..." + # Copy everything from /var/default to /var/www/html + cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files + chown -R laravel:laravel /var/www/html/ + chmod -R 755 /var/www/html/ +fi -# Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) +# Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder). cp -n /var/default/.env.example /var/www/html/.env # Use -n to avoid overwriting existing files # Check and copy default Nginx configuration if not exists From 1cb03f5e85255fc23ddd077ed02a04c3382fc790 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:34:21 +0200 Subject: [PATCH 379/514] Add logging to troubleshoot --- docker/standalone/scripts/startup.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 51e1eb328..5637a5e7e 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -25,9 +25,15 @@ log_message() { echo "$1" } +log_message "Permissions of /var/www/html:" +ls -la /var/www/html + # The issue seems that "index.nginx-debian.html" seems to be in the folder, hence the next check will always fail. rm /var/www/html/index.nginx-debian.html +log_message "Permissions of /var/www/html:" +ls -la /var/www/html + # Check if project folder is empty. if [ -z "$(ls -A /var/www/html)" ]; then chown -R laravel:laravel /var/www/html/ From 06a67fe8d2fc7582a5b33c64552e30f5568158c3 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:43:40 +0200 Subject: [PATCH 380/514] Check for public folder instead of if has contents --- docker/standalone/scripts/startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 5637a5e7e..3811faea5 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -35,7 +35,7 @@ log_message "Permissions of /var/www/html:" ls -la /var/www/html # Check if project folder is empty. -if [ -z "$(ls -A /var/www/html)" ]; then +if [ ! -d "/var/www/html/public" ]; then chown -R laravel:laravel /var/www/html/ chmod -R 777 /var/www/html/ log_message "Warning: project folder is empty. Copying default files..." From 9188c42693fa7ea9cfcccb45ce148576a3ed2400 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:49:33 +0200 Subject: [PATCH 381/514] Remove unnecessary deletion of file --- docker/standalone/scripts/startup.sh | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 3811faea5..664702eb2 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -25,16 +25,7 @@ log_message() { echo "$1" } -log_message "Permissions of /var/www/html:" -ls -la /var/www/html - -# The issue seems that "index.nginx-debian.html" seems to be in the folder, hence the next check will always fail. -rm /var/www/html/index.nginx-debian.html - -log_message "Permissions of /var/www/html:" -ls -la /var/www/html - -# Check if project folder is empty. +# Check if public folder is exists. If not, copy project. if [ ! -d "/var/www/html/public" ]; then chown -R laravel:laravel /var/www/html/ chmod -R 777 /var/www/html/ From ebe730bfef2aefc38f4695d7356e802d7e9f8702 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:52:50 +0200 Subject: [PATCH 382/514] Test if permissions are necessary --- docker/standalone/Dockerfile | 3 --- docker/standalone/README.md | 2 +- docker/standalone/scripts/startup.sh | 4 ++-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/docker/standalone/Dockerfile b/docker/standalone/Dockerfile index 474b7a98e..a16bed8cb 100644 --- a/docker/standalone/Dockerfile +++ b/docker/standalone/Dockerfile @@ -50,9 +50,6 @@ RUN mkdir -p /var/log/nginx && chown -R laravel:laravel /var/log/nginx # Expose ports EXPOSE 80 443 -# Copy .env file for it to be available when starting the Docker container (to be able to bind-mount it to the host, instead of the entire project folder) -#COPY --chown=laravel:laravel /var/default/.env.example /var/www/html/.env - # Copy startup script COPY --chown=laravel:laravel ./docker/standalone/scripts/startup.sh /usr/local/bin/startup-script.sh # Make startup script executable diff --git a/docker/standalone/README.md b/docker/standalone/README.md index 5a11a24c2..40c740a38 100644 --- a/docker/standalone/README.md +++ b/docker/standalone/README.md @@ -9,7 +9,7 @@ Once you have Docker installed, you can run CtrlPanel standalone Docker by execu Recommended way via Docker Compose: -1. Copy and configure your docker compose file to your needs `curl -L -o docker-compose.yml https://raw.githubusercontent.com/Ctrlpanel-gg/panel/blob/main/docker/standalone/compose.yaml`. +1. Copy and configure your docker compose file to your needs `curl -L -o compose.yaml https://raw.githubusercontent.com/Ctrlpanel-gg/panel/blob/main/docker/standalone/compose.yaml`. 2. Create the env file in the same directory as the compose file `touch env_file`. 3. When installing you need to update the `env_file` file. Change those two variables to: `MEMCACHED_HOST=redis` and `REDIS_HOST=redis`, to use the Redis server which comes with the docker compose installation. diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 664702eb2..233650626 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -27,8 +27,8 @@ log_message() { # Check if public folder is exists. If not, copy project. if [ ! -d "/var/www/html/public" ]; then - chown -R laravel:laravel /var/www/html/ - chmod -R 777 /var/www/html/ +# chown -R laravel:laravel /var/www/html/ +# chmod -R 777 /var/www/html/ log_message "Warning: project folder is empty. Copying default files..." # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files From 9e6688dd531b53da561ea8ab8e8026b5bb6b3380 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:56:16 +0200 Subject: [PATCH 383/514] Remove unnecessary permissions --- docker/standalone/scripts/startup.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/standalone/scripts/startup.sh b/docker/standalone/scripts/startup.sh index 233650626..bf11b5bff 100644 --- a/docker/standalone/scripts/startup.sh +++ b/docker/standalone/scripts/startup.sh @@ -27,8 +27,6 @@ log_message() { # Check if public folder is exists. If not, copy project. if [ ! -d "/var/www/html/public" ]; then -# chown -R laravel:laravel /var/www/html/ -# chmod -R 777 /var/www/html/ log_message "Warning: project folder is empty. Copying default files..." # Copy everything from /var/default to /var/www/html cp -nr /var/default/. /var/www/html # Use -n to avoid overwriting existing files From 44c05b0f6e2e56b2b95bc89baa24264be9ca3a1a Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:58:35 +0200 Subject: [PATCH 384/514] Update compose.yaml with only logs and env file --- docker/standalone/compose.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index 6b00f27df..76e64b13b 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -9,10 +9,8 @@ services: - "80:80" - "443:443" volumes: -# - './env:/var/www/html/.env:rw' -# - './logs:/var/www/html/storage/logs:rw' - - './website_files:/var/www/html:rw' # change it -# - './nginx_config:/etc/nginx/conf.d/:rw' # change it + - './logs:/var/www/html/storage/logs:w' + - './env_file:/var/www/html/.env' networks: - controlpanel From 33096fbea4b156de3fbd6091ce29cca3018621f1 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 8 Jun 2024 08:36:41 +0200 Subject: [PATCH 385/514] Rename controlpanel to ctrlpanel --- docker/development/compose.yaml | 22 +++++++++++----------- docker/standalone/compose.yaml | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docker/development/compose.yaml b/docker/development/compose.yaml index 7f5acb0f8..fd1f09bab 100644 --- a/docker/development/compose.yaml +++ b/docker/development/compose.yaml @@ -1,10 +1,10 @@ services: # TODO: add wings and pterodactyl - controlpanel_panel: + ctrlpanel_panel: build: context: ../../ dockerfile: ./docker/standalone/Dockerfile - container_name: controlpanel_development + container_name: ctrlpanel_development restart: unless-stopped ports: - "80:80" @@ -13,11 +13,11 @@ services: - '../..:/var/www/html:rw' - './nginx_config:/etc/nginx/conf.d/:rw' networks: - - controlpanel + - ctrlpanel mysql: image: mysql - container_name: controlpanel_mysql + container_name: ctrlpanel_mysql restart: unless-stopped tty: true ports: @@ -30,31 +30,31 @@ services: volumes: - "./mysql:/var/lib/mysql:delegated" networks: - - controlpanel + - ctrlpanel phpmyadmin: image: phpmyadmin/phpmyadmin - container_name: controlpanel_phpmyadmin + container_name: ctrlpanel_phpmyadmin depends_on: - mysql ports: - '8080:80' environment: - - PMA_HOST=controlpanel_mysql + - PMA_HOST=ctrlpanel_mysql - PMA_USER=root - PMA_PASSWORD=root - PMA_ARBITRARY=1 networks: - - controlpanel + - ctrlpanel redis: image: redis - container_name: controlpanel_redis + container_name: ctrlpanel_redis restart: unless-stopped ports: - "6379:6379" networks: - - controlpanel + - ctrlpanel networks: - controlpanel: + ctrlpanel: diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index 76e64b13b..496e6c0f8 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -1,7 +1,7 @@ services: - controlpanel: + ctrlpanel: image: ghcr.io/ctrlpanel-gg/panel:latest - container_name: controlpanel_panel + container_name: ctrlpanel_panel restart: unless-stopped depends_on: - redis @@ -12,11 +12,11 @@ services: - './logs:/var/www/html/storage/logs:w' - './env_file:/var/www/html/.env' networks: - - controlpanel + - ctrlpanel mysql: image: mysql - container_name: controlpanel_mysql + container_name: ctrlpanel_mysql restart: unless-stopped tty: true ports: @@ -29,32 +29,32 @@ services: volumes: - "./mysql:/var/lib/mysql:delegated" networks: - - controlpanel + - ctrlpanel phpmyadmin: image: phpmyadmin/phpmyadmin - container_name: controlpanel_phpmyadmin + container_name: ctrlpanel_phpmyadmin restart: unless-stopped depends_on: - mysql ports: - '8080:80' environment: - - PMA_HOST=controlpanel_mysql + - PMA_HOST=ctrlpanel_mysql - PMA_USER=root # change it - PMA_PASSWORD=root # change it - PMA_ARBITRARY=1 networks: - - controlpanel + - ctrlpanel redis: image: redis - container_name: controlpanel_redis + container_name: ctrlpanel_redis restart: unless-stopped ports: - "6379:6379" networks: - - controlpanel + - ctrlpanel networks: - controlpanel: + ctrlpanel: From 6a5f05e4192ddfdaef738e8dae05a52e9482e81f Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 8 Jun 2024 08:40:43 +0200 Subject: [PATCH 386/514] Fix typo --- themes/default/views/information/tos-content.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/default/views/information/tos-content.blade.php b/themes/default/views/information/tos-content.blade.php index e9a42927f..cec69f4ad 100644 --- a/themes/default/views/information/tos-content.blade.php +++ b/themes/default/views/information/tos-content.blade.php @@ -18,7 +18,7 @@ <ol> <li>AGREEMENT TO TERMS</li> - <li>NTELLECTUAL PROPERTY RIGHTS</li> + <li>INTELLECTUAL PROPERTY RIGHTS</li> <li>USER REPRESENTATIONS</li> <li>USER REGISTRATION</li> <li>PRODUCTS</li> From 05dc11ed6f608d01452db8f1d23df55188255acb Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 8 Jun 2024 17:24:16 +0200 Subject: [PATCH 387/514] Update Readme and stanalone compose --- docker/standalone/README.md | 16 ++++++++-------- docker/standalone/compose.yaml | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docker/standalone/README.md b/docker/standalone/README.md index 40c740a38..2efaf6bfd 100644 --- a/docker/standalone/README.md +++ b/docker/standalone/README.md @@ -7,19 +7,19 @@ If you're using a different operating system, you can follow the official Docker Once you have Docker installed, you can run CtrlPanel standalone Docker by executing the following command: -Recommended way via Docker Compose: - -1. Copy and configure your docker compose file to your needs `curl -L -o compose.yaml https://raw.githubusercontent.com/Ctrlpanel-gg/panel/blob/main/docker/standalone/compose.yaml`. -2. Create the env file in the same directory as the compose file `touch env_file`. -3. When installing you need to update the `env_file` file. Change those two variables to: `MEMCACHED_HOST=redis` and `REDIS_HOST=redis`, to use the Redis server which comes with the docker compose installation. - Running as commandline command: ```bash -docker run -p 80:80 -p 443:443 -v /path/to/website_files:/var/www/html -v /path/to/nginx_config:/etc/nginx/conf.d/ ghcr.io/ctrlpanel-gg/panel:latest +docker run -p 80:80 -p 443:443 -v /path/to/website_files:/var/www/html ghcr.io/ctrlpanel-gg/panel:latest ``` -This command will run the latest CtrlPanel Docker image from Docker Hub and run it. +This command will run the latest CtrlPanel Docker image from GitHub Container Registry and run it. + +Recommended way via Docker Compose: + +1. Copy and configure your docker compose file to your needs `curl -L -o compose.yaml https://raw.githubusercontent.com/Ctrlpanel-gg/panel/blob/main/docker/standalone/compose.yaml`. +2. Create the env file in the same directory as the compose file `touch env_file`. +3. When installing you need to update the `env_file` file. Change those two variables to: `MEMCACHED_HOST=redis` and `REDIS_HOST=redis`, to use the Redis server which comes with the docker compose installation. The control panel will be available at http://localhost/install and will be a completely fresh installation. diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index 496e6c0f8..0f29febae 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -11,6 +11,7 @@ services: volumes: - './logs:/var/www/html/storage/logs:w' - './env_file:/var/www/html/.env' + #- './website_files:/var/www/html:rw' # optionally use this, in case you want/need access to all project files, to use like addons/plugins. networks: - ctrlpanel From d210baa7d26caa591471ddca15de82722b93f092 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 8 Jun 2024 21:12:52 +0200 Subject: [PATCH 388/514] Docker compose default add project files --- docker/standalone/compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index 0f29febae..f9a3ac71e 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -11,7 +11,7 @@ services: volumes: - './logs:/var/www/html/storage/logs:w' - './env_file:/var/www/html/.env' - #- './website_files:/var/www/html:rw' # optionally use this, in case you want/need access to all project files, to use like addons/plugins. + - './website_files:/var/www/html:rw' # optionally use remove this bind mount, it's not needed unless you want access to all project files, to modify the project with addons/plugins. networks: - ctrlpanel From 01ca75fe4af8809e82e7f383c644e4cc9d19db98 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 8 Jun 2024 21:24:17 +0200 Subject: [PATCH 389/514] Fix typo --- docker/standalone/compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index f9a3ac71e..adb572b37 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -11,7 +11,7 @@ services: volumes: - './logs:/var/www/html/storage/logs:w' - './env_file:/var/www/html/.env' - - './website_files:/var/www/html:rw' # optionally use remove this bind mount, it's not needed unless you want access to all project files, to modify the project with addons/plugins. + - './website_files:/var/www/html:rw' # optionally remove this bind mount, it's not needed unless you want access to all project files, to modify the project with addons/plugins. networks: - ctrlpanel From a58aa10fffa298d465118266626ecd5d9389920b Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 8 Jun 2024 22:41:55 +0200 Subject: [PATCH 390/514] Readd nginx config expose --- docker/standalone/compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index adb572b37..e1c2b7a61 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -12,6 +12,7 @@ services: - './logs:/var/www/html/storage/logs:w' - './env_file:/var/www/html/.env' - './website_files:/var/www/html:rw' # optionally remove this bind mount, it's not needed unless you want access to all project files, to modify the project with addons/plugins. + - './nginx_config:/etc/nginx/conf.d/:rw' # optionally remove this bind mount, it's not needed unless you want to modify the project with addons/plugins. (dangerous to edit) networks: - ctrlpanel From 628f2c6cb81f5d50493a751408c4ccb110b7cd0b Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 9 Jun 2024 16:48:30 +0200 Subject: [PATCH 391/514] Update dev docker service name --- docker/development/compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/development/compose.yaml b/docker/development/compose.yaml index fd1f09bab..1e8571a05 100644 --- a/docker/development/compose.yaml +++ b/docker/development/compose.yaml @@ -1,6 +1,6 @@ services: # TODO: add wings and pterodactyl - ctrlpanel_panel: + ctrlpanel_development: build: context: ../../ dockerfile: ./docker/standalone/Dockerfile From bb9bc296946513c73ac10047bbc30689529fcb95 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 9 Jun 2024 16:51:04 +0200 Subject: [PATCH 392/514] Update standalone docker service name --- docker/standalone/compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/standalone/compose.yaml b/docker/standalone/compose.yaml index e1c2b7a61..b2d3ce2f4 100644 --- a/docker/standalone/compose.yaml +++ b/docker/standalone/compose.yaml @@ -1,7 +1,7 @@ services: - ctrlpanel: + ctrlpanel_standalone: image: ghcr.io/ctrlpanel-gg/panel:latest - container_name: ctrlpanel_panel + container_name: ctrlpanel_standalone restart: unless-stopped depends_on: - redis From 392b6011ba021b76e659a57b536fc49621d3fb64 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Sun, 9 Jun 2024 18:38:50 +0200 Subject: [PATCH 393/514] refactor: added warning and deleted useless check in installer first step --- public/install/functions.php | 30 ------------------------------ public/install/index.php | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 40 deletions(-) diff --git a/public/install/functions.php b/public/install/functions.php index 01dbcd04d..c23856e86 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -48,36 +48,6 @@ function checkWriteable(): bool return is_writable('../../.env'); } -/** - * Check if the server runs using HTTPS - * @return bool Returns true on HTTPS or false on HTTP. - */ -function checkHTTPS(): bool -{ - $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443; - wh_log('https:', 'debug', (array)$isHttps); - return $isHttps; -} - -/** - * Check if MySQL is installed and runs the correct version using a shell command - * @return mixed|string 'OK' if required version is met, returns MySQL version if not met. - */ -function getMySQLVersion(): mixed -{ - global $requirements; - - wh_log('attempting to get mysql version', 'debug'); - - $output = shell_exec('mysql -V') ?? ''; - preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); - - $versionoutput = $version[0] ?? '0'; - wh_log('mysql version: ' . $versionoutput, 'debug'); - - return intval($versionoutput) > intval($requirements['mysql']) ? 'OK' : $versionoutput; -} - /** * Check if zip is installed using a shell command * @return string 'OK' on success and 'not OK' on failure. diff --git a/public/install/index.php b/public/install/index.php index 8d63f71d9..37347ce6f 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -72,18 +72,12 @@ function cardStart($title, $subtitle = null) <ul class="list-none mb-2"> - <li class="<?php echo checkHTTPS() == true ? 'ok' : 'not-ok'; ?> check">HTTPS is required</li> - <li class="<?php echo checkWriteable() == true ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) </li> - <li class="<?php echo getMySQLVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - mysql version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>) - </li> - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> Missing php-extentions: <?php echo count(checkExtensions()) == 0 ? 'none' : ''; @@ -93,10 +87,6 @@ function cardStart($title, $subtitle = null) echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?> </li> - - <!-- <li class="<?php echo getZipVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Zip - version: <?php echo getZipVersion(); ?> </li> --> - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Git version: <?php echo getGitVersion(); ?> @@ -106,6 +96,16 @@ function cardStart($title, $subtitle = null) Tar version: <?php echo getTarVersion(); ?> </li> + + <li> + <p class="text-neutral-400 mb-1"> + <br> + <span style="color: #eab308;">Important:</span> + CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> + Please make sure you have these installed and running before you continue. + </p> + </li> + </ul> </div> From a6330ad09d36ab9fac8b7b4a053bcb6d878e5e4c Mon Sep 17 00:00:00 2001 From: S0ly <86328249+S0ly@users.noreply.github.com> Date: Sun, 9 Jun 2024 18:43:22 +0200 Subject: [PATCH 394/514] fix: SECURITY.md --- .github/SECURITY.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 7aa6d156e..ef6b07aa9 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -11,9 +11,7 @@ At this time, we only accept vulnerability reports through GitHub Advisories. We kindly ask that you do not submit reports via other third-party bug bounty platforms, as they will be disregarded. ## Supported Versions - -### CtrlPanel Versions - - Latests +### CtrlPanel Versions We strongly recommend using or upgrading to the latest version of CtrlPanel to ensure you have access to the latest security fixes and enhancements. From 010c615fd2e31390d13ea74c7525eacd0959b0b3 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Sun, 9 Jun 2024 18:57:16 +0200 Subject: [PATCH 395/514] docs: updated main readme to change links and modify details --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9c5dcfb90..5237de3cf 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ CtrlPanel offers an easy-to-use and free billing solution for all starting and experienced hosting providers that seamlessly integrates with the Pterodactyl panel. It facilitates account creation, server ordering, and management, while offering addons, multiple payment methods, and customizable themes for a comprehensive solution. - +  - -[](https://crowdin.com/project/controlpanelgg) - + <!-- +this need update --> <!-- [](https://crowdin.com/project/controlpanelgg) --> +   @@ -35,13 +35,13 @@ Try it! Demo Server: [demo.CtrlPanel.gg](https://demo.CtrlPanel.gg) -<!-- It is a temporary live demo; all data will be deleted. --> +*It is a temporary live demo; all data will be deleted.* ## 🔧 How to Install ### 🐳 Docker -Soon... +*Soon...* <!-- ```bash docker run ... @@ -57,7 +57,6 @@ Requirements: - Platform - Major Linux distros such as Debian, Ubuntu, CentOS, Fedora, and ArchLinux etc. - - Windows 10 (x64), Windows Server ... Follow the [documentation](https://ctrlpanel.gg/docs/intro) to know how to install. From 07355304b8ff5fbf83d3558b0d835fc47ef2ab29 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 11 Jun 2024 23:05:44 +0200 Subject: [PATCH 396/514] Add redis configuration to installer --- public/install/forms.php | 49 ++-- public/install/functions.php | 6 +- public/install/index.php | 497 +++++++++++++++++++---------------- 3 files changed, 307 insertions(+), 245 deletions(-) diff --git a/public/install/forms.php b/public/install/forms.php index 9bed6eda2..5439ac891 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -48,22 +48,6 @@ header('LOCATION: index.php?step=2.5'); } -if (isset($_POST['checkGeneral'])) { - wh_log('setting app settings', 'debug'); - $appname = '"' . $_POST['name'] . '"'; - $appurl = $_POST['url']; - - if (substr($appurl, -1) === '/') { - $appurl = substr_replace($appurl, '', -1); - } - - setenv('APP_NAME', $appname); - setenv('APP_URL', $appurl); - - wh_log('App settings set', 'debug'); - header('LOCATION: index.php?step=4'); -} - if (isset($_POST['feedDB'])) { wh_log('Feeding the Database', 'debug'); $logs = ''; @@ -84,13 +68,44 @@ wh_log($logs, 'debug'); wh_log('Feeding the Database successful', 'debug'); - header('LOCATION: index.php?step=3'); + header('LOCATION: index.php?step=2.6'); } catch (\Throwable $th) { wh_log('Feeding the Database failed', 'error'); header("LOCATION: index.php?step=2.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); } } +if (isset($_POST['redisSetup'])) { + wh_log('Setting up Redis', 'debug'); + $redisHost = $_POST['redishost']; + $redisPort = $_POST['redisport']; + $redisPassword = $_POST['redispassword']; + + setenv('MEMCACHED_HOST', $redisHost); + setenv('REDIS_HOST', $redisHost); + setenv('REDIS_PORT', $redisPort); + setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); + + wh_log('Redis settings set', 'debug'); + header('LOCATION: index.php?step=3'); +} + +if (isset($_POST['checkGeneral'])) { + wh_log('setting app settings', 'debug'); + $appname = '"' . $_POST['name'] . '"'; + $appurl = $_POST['url']; + + if (substr($appurl, -1) === '/') { + $appurl = substr_replace($appurl, '', -1); + } + + setenv('APP_NAME', $appname); + setenv('APP_URL', $appurl); + + wh_log('App settings set', 'debug'); + header('LOCATION: index.php?step=4'); +} + if (isset($_POST['checkSMTP'])) { wh_log('Checking SMTP Settings', 'debug'); try { diff --git a/public/install/functions.php b/public/install/functions.php index 01dbcd04d..ea1a1af60 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -243,7 +243,6 @@ function run_console(string $command, array $descriptors = null, string $cwd = n if ($exit_code > 0) { wh_log('command result: ' . $output, 'error'); throw new Exception("There was an error after running command `$command`", $exit_code); - return $output; } else { return $output; } @@ -303,3 +302,8 @@ function generateRandomString(int $length = 8): string return $randomString; } + +function determineIfRunningInDocker(): bool +{ + return file_exists('/.dockerenv'); +} diff --git a/public/install/index.php b/public/install/index.php index 8d63f71d9..3c1483d44 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -63,66 +63,148 @@ function cardStart($title, $subtitle = null) <body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> - <?php +<?php - // Getting started - if (!isset($_GET['step']) || $_GET['step'] == 1) { +// Getting started +if (!isset($_GET['step']) || $_GET['step'] == 1) { ?> - <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> + <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> - <ul class="list-none mb-2"> + <ul class="list-none mb-2"> - <li class="<?php echo checkHTTPS() == true ? 'ok' : 'not-ok'; ?> check">HTTPS is required</li> + <li class="<?php echo checkHTTPS() == true ? 'ok' : 'not-ok'; ?> check">HTTPS is required</li> - <li class="<?php echo checkWriteable() == true ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> + <li class="<?php echo checkWriteable() == true ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> - <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) - </li> + <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) + </li> - <li class="<?php echo getMySQLVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - mysql version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>) - </li> + <li class="<?php echo getMySQLVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + mysql version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>) + </li> - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> - Missing php-extentions: - <?php echo count(checkExtensions()) == 0 ? 'none' : ''; - foreach (checkExtensions() as $ext) { - echo $ext . ', '; - } - echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?> - </li> + <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> + Missing php-extentions: + <?php echo count(checkExtensions()) == 0 ? 'none' : ''; + foreach (checkExtensions() as $ext) { + echo $ext . ', '; + } + echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?> + </li> - <!-- <li class="<?php echo getZipVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Zip + <!-- <li class="<?php echo getZipVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Zip version: <?php echo getZipVersion(); ?> </li> --> - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Git version: - <?php echo getGitVersion(); ?> - </li> + <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Git version: + <?php echo getGitVersion(); ?> + </li> + + <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Tar version: + <?php echo getTarVersion(); ?> + </li> + </ul> + + </div> + <a href="?step=2" class="w-full flex justify-center"> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500">Lets + go</button> + </a> + + <?php +} + +// DB Config +if (isset($_GET['step']) && $_GET['step'] == 2) { + + echo cardStart($title = "Database Configuration"); ?> + + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasedriver">Database Driver</label> + <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required value="mysql" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasehost">Database Host</label> + <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required value="<?php echo (determineIfRunningInDocker() ? 'mysql' : '127.0.0.1')?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseport">Database Port</label> + <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required value="3306" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuser">Database User</label> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="ctrlpaneluser" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuserpass">Database User Password</label> + <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" required class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none "> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col"> + <label for="database">Database</label> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + </div> - <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Tar version: - <?php echo getTarVersion(); ?> - </li> - </ul> + </div> </div> - <a href="?step=2" class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500">Lets - go</button> - </a> + <div class="w-full flex justify-center"> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB">Submit</button> + </div> + </form> + </div> + + <?php +} +// DB Migration & APP_KEY Generation +if (isset($_GET['step']) && $_GET['step'] == 2.5) { ?> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> + + <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + </div> + <div class="w-full flex justify-center"> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB">Submit</button> + </div> + </form> <?php } - // DB Config - if (isset($_GET['step']) && $_GET['step'] == 2) { + // Redis Config + if (isset($_GET['step']) && $_GET['step'] == 2.6) { - echo cardStart($title = "Database Configuration"); ?> + echo cardStart($title = "Redis Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="redisSetup"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -131,71 +213,32 @@ function cardStart($title, $subtitle = null) <div class="col-md-12"> <div class="form-group"> <div class="flex flex-col mb-3"> - <label for="databasedriver">Database Driver</label> - <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required value="mysql" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasehost">Database Host</label> - <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required value="127.0.0.1" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseport">Database Port</label> - <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required value="3306" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <label for="redishost">Redis Host</label> + <input x-model="redishost" id="redishost" name="redishost" type="text" required value="<?php echo (determineIfRunningInDocker() ? 'redis' : '127.0.0.1')?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> <div class="form-group"> <div class="flex flex-col mb-3"> - <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="ctrlpaneluser" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <label for="redisport">Redis Port</label> + <input x-model="redisport" id="redisport" name="redisport" type="number" required value="6379" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> <div class="form-group"> <div class="flex flex-col mb-3"> - <label for="databaseuserpass">Database User Password</label> - <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" required class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none "> + <label for="redispassword">Redis Password (optionally, only if configured)</label> + <input x-model="redispassword" id="redispassword" name="redispassword" type="text" placeholder="usually can be left blank" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> - - <div class="form-group"> - <div class="flex flex-col"> - <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - </div> - </div> </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB">Submit</button> + <div class="w-full flex justify-center"> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="redisSetup">Submit</button> </div> </form> </div> - <?php - } - - // DB Migration & APP_KEY Generation - if (isset($_GET['step']) && $_GET['step'] == 2.5) { ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> - - <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> - - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB">Submit</button> - </div> - </form> <?php } @@ -204,38 +247,38 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "Dashboard Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkGeneral"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkGeneral"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="database">Dashboard URL</label> - <input id="url" name="url" type="text" required value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="database">Dashboard URL</label> + <input id="url" name="url" type="text" required value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="name">Dashboard Name</label> - <input id="name" name="name" type="text" required value="CtrlPanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="name">Dashboard Name</label> + <input id="name" name="name" type="text" required value="CtrlPanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - </div> - </div> </div> + </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral">Submit</button> - </div> - </form> </div> + <div class="w-full flex justify-center"> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral">Submit</button> + </div> + </form> + </div> + <?php } @@ -245,79 +288,79 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "E-Mail Configuration", $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkSMTP"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="method">Your E-Mail Method</label> - <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="smtp" selected>SMTP</option> - </select> - </div> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkSMTP"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="method">Your E-Mail Method</label> + <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="smtp" selected>SMTP</option> + </select> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="host">Your Mailer-Host</label> - <input id="host" name="host" type="text" required value="smtp.google.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="host">Your Mailer-Host</label> + <input id="host" name="host" type="text" required value="smtp.google.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="port">Your Mail Port</label> - <input id="port" name="port" type="number" required value="567" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="port">Your Mail Port</label> + <input id="port" name="port" type="number" required value="567" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="user">Your Mail User</label> - <input id="user" name="user" type="text" required value="info@mydomain.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="user">Your Mail User</label> + <input id="user" name="user" type="text" required value="info@mydomain.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Your Mail-User Password</label> - <input id="pass" name="pass" type="password" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Your Mail-User Password</label> + <input id="pass" name="pass" type="password" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="encryption">Your Mail encryption method</label> - <select id="encryption" name="encryption" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="tls" selected>TLS</option> - <option value="ssl">SSL</option> - <option value="null">None</option> - </select> - </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="encryption">Your Mail encryption method</label> + <select id="encryption" name="encryption" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="tls" selected>TLS</option> + <option value="ssl">SSL</option> + <option value="null">None</option> + </select> </div> - </div> + </div> - </div> - </div> + </div> - <div class="flex w-full justify-around mt-4 gap-8 px-8"> - <button type="submit" class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP">Submit</button> + </div> - <a href="?step=5" class="w-full"> - <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600">Skip - For Now</button> - </a> - </div> - </form> + <div class="flex w-full justify-around mt-4 gap-8 px-8"> + <button type="submit" class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP">Submit</button> + + <a href="?step=5" class="w-full"> + <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600">Skip + For Now</button> + </a> </div> + </form> + </div> <?php @@ -328,47 +371,47 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "Pterodactyl Configuration", $subtitle = "Lets get some info about your Pterodactyl Installation!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkPtero"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkPtero"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> - <label for="url">Pterodactyl URL</label> - <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <label for="url">Pterodactyl URL</label> + <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="key">Application API Key</label> - <input id="key" name="key" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/admin/api] <br /> The key needs all + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="key">Application API Key</label> + <input id="key" name="key" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">[Found at: ptero.example.com/admin/api] <br /> The key needs all Read & Write permissions! </span> - </div> </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="clientkey">Admin User Client API Key</label> - <input id="clientkey" name="clientkey" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/account/api] <br /> Your Account + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="clientkey">Admin User Client API Key</label> + <input id="clientkey" name="clientkey" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">[Found at: ptero.example.com/account/api] <br /> Your Account needs to be an Admin!</span> - </div> </div> - - </div> + </div> - </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero">Submit</button> - </div> - </form> + + </div> </div> + <div class="w-full flex justify-center"> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero">Submit</button> + </div> + </form> + </div> <?php @@ -379,45 +422,45 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "First Admin Creation", $subtitle = "Lets create the first admin user!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="createUser"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="createUser"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pteroID">Pterodactyl User ID </label> - <input id="pteroID" name="pteroID" type="text" required value="1" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pteroID">Pterodactyl User ID </label> + <input id="pteroID" name="pteroID" type="text" required value="1" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> </div> + </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Password</label> - <input id="pass" name="pass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="repass">Confirm Password</label> - <input id="repass" name="repass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Password</label> + <input id="pass" name="pass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> </div> - + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="repass">Confirm Password</label> + <input id="repass" name="repass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> + </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser">Submit</button> - </div> - </form> + <div class="w-full flex justify-center"> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser">Submit</button> </div> + </form> + </div> + <?php } @@ -431,16 +474,16 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "Installation Complete!", $subtitle = "You may navigate to your Dashboard now and log in!"); ?> - <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center "> - <button class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500">Lets - Go!</button> - </a> + <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> + <button class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500">Lets + Go!</button> + </a> - </div> - </div> + </div> + </div> <?php } - ?> + ?> </body> -</html> \ No newline at end of file +</html> From 8d0690d2b318576c731b251d4bf99beb3ce6f88c Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 11 Jun 2024 23:08:20 +0200 Subject: [PATCH 397/514] Revert unnecessary changes in index.php --- public/install/index.php | 472 +++++++++++++++++++-------------------- 1 file changed, 236 insertions(+), 236 deletions(-) diff --git a/public/install/index.php b/public/install/index.php index 3c1483d44..9add8d5ac 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -63,140 +63,140 @@ function cardStart($title, $subtitle = null) <body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> -<?php + <?php -// Getting started -if (!isset($_GET['step']) || $_GET['step'] == 1) { + // Getting started + if (!isset($_GET['step']) || $_GET['step'] == 1) { ?> - <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> + <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> - <ul class="list-none mb-2"> + <ul class="list-none mb-2"> - <li class="<?php echo checkHTTPS() == true ? 'ok' : 'not-ok'; ?> check">HTTPS is required</li> + <li class="<?php echo checkHTTPS() == true ? 'ok' : 'not-ok'; ?> check">HTTPS is required</li> - <li class="<?php echo checkWriteable() == true ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> + <li class="<?php echo checkWriteable() == true ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> - <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) - </li> + <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) + </li> - <li class="<?php echo getMySQLVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - mysql version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>) - </li> + <li class="<?php echo getMySQLVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + mysql version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>) + </li> - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> - Missing php-extentions: - <?php echo count(checkExtensions()) == 0 ? 'none' : ''; - foreach (checkExtensions() as $ext) { - echo $ext . ', '; - } - echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?> - </li> + <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> + Missing php-extentions: + <?php echo count(checkExtensions()) == 0 ? 'none' : ''; + foreach (checkExtensions() as $ext) { + echo $ext . ', '; + } + echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?> + </li> - <!-- <li class="<?php echo getZipVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Zip + <!-- <li class="<?php echo getZipVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Zip version: <?php echo getZipVersion(); ?> </li> --> - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Git version: - <?php echo getGitVersion(); ?> - </li> + <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Git version: + <?php echo getGitVersion(); ?> + </li> - <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Tar version: - <?php echo getTarVersion(); ?> - </li> - </ul> + <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Tar version: + <?php echo getTarVersion(); ?> + </li> + </ul> - </div> - <a href="?step=2" class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500">Lets - go</button> - </a> + </div> + <a href="?step=2" class="w-full flex justify-center"> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500">Lets + go</button> + </a> <?php -} + } -// DB Config -if (isset($_GET['step']) && $_GET['step'] == 2) { + // DB Config + if (isset($_GET['step']) && $_GET['step'] == 2) { - echo cardStart($title = "Database Configuration"); ?> + echo cardStart($title = "Database Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasedriver">Database Driver</label> - <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required value="mysql" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasedriver">Database Driver</label> + <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required value="mysql" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasehost">Database Host</label> - <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required value="<?php echo (determineIfRunningInDocker() ? 'mysql' : '127.0.0.1')?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasehost">Database Host</label> + <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required value="127.0.0.1" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseport">Database Port</label> - <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required value="3306" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseport">Database Port</label> + <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required value="3306" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="ctrlpaneluser" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuser">Database User</label> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="ctrlpaneluser" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuserpass">Database User Password</label> - <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" required class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none "> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuserpass">Database User Password</label> + <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" required class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none "> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col"> + <label for="database">Database</label> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> + </div> </div> + </div> + <div class="w-full flex justify-center "> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB">Submit</button> + </div> + </form> </div> - </div> - <div class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB">Submit</button> - </div> - </form> - </div> - <?php -} + } -// DB Migration & APP_KEY Generation -if (isset($_GET['step']) && $_GET['step'] == 2.5) { ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> + // DB Migration & APP_KEY Generation + if (isset($_GET['step']) && $_GET['step'] == 2.5) { ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> - <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> + <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - </div> - <div class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB">Submit</button> - </div> - </form> - <?php + </div> + <div class="w-full flex justify-center "> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB">Submit</button> + </div> + </form> + <?php } // Redis Config @@ -247,37 +247,37 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "Dashboard Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkGeneral"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkGeneral"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="database">Dashboard URL</label> - <input id="url" name="url" type="text" required value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="database">Dashboard URL</label> + <input id="url" name="url" type="text" required value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="name">Dashboard Name</label> - <input id="name" name="name" type="text" required value="CtrlPanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col"> + <label for="name">Dashboard Name</label> + <input id="name" name="name" type="text" required value="CtrlPanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> + </div> </div> - </div> - </div> + </div> - <div class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral">Submit</button> + <div class="w-full flex justify-center "> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral">Submit</button> + </div> + </form> </div> - </form> - </div> <?php @@ -288,79 +288,79 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "E-Mail Configuration", $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkSMTP"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="method">Your E-Mail Method</label> - <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="smtp" selected>SMTP</option> - </select> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkSMTP"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="method">Your E-Mail Method</label> + <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="smtp" selected>SMTP</option> + </select> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="host">Your Mailer-Host</label> - <input id="host" name="host" type="text" required value="smtp.google.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="host">Your Mailer-Host</label> + <input id="host" name="host" type="text" required value="smtp.google.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="port">Your Mail Port</label> - <input id="port" name="port" type="number" required value="567" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="port">Your Mail Port</label> + <input id="port" name="port" type="number" required value="567" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="user">Your Mail User</label> - <input id="user" name="user" type="text" required value="info@mydomain.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="user">Your Mail User</label> + <input id="user" name="user" type="text" required value="info@mydomain.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Your Mail-User Password</label> - <input id="pass" name="pass" type="password" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Your Mail-User Password</label> + <input id="pass" name="pass" type="password" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="encryption">Your Mail encryption method</label> - <select id="encryption" name="encryption" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="tls" selected>TLS</option> - <option value="ssl">SSL</option> - <option value="null">None</option> - </select> + <div class="form-group"> + <div class="flex flex-col"> + <label for="encryption">Your Mail encryption method</label> + <select id="encryption" name="encryption" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="tls" selected>TLS</option> + <option value="ssl">SSL</option> + <option value="null">None</option> + </select> + </div> </div> - </div> - </div> + </div> - </div> + </div> - </div> + </div> - <div class="flex w-full justify-around mt-4 gap-8 px-8"> - <button type="submit" class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP">Submit</button> + <div class="flex w-full justify-around mt-4 gap-8 px-8"> + <button type="submit" class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP">Submit</button> - <a href="?step=5" class="w-full"> - <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600">Skip - For Now</button> - </a> + <a href="?step=5" class="w-full"> + <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600">Skip + For Now</button> + </a> + </div> + </form> </div> - </form> - </div> <?php @@ -371,47 +371,47 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "Pterodactyl Configuration", $subtitle = "Lets get some info about your Pterodactyl Installation!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkPtero"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkPtero"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> - <label for="url">Pterodactyl URL</label> - <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <label for="url">Pterodactyl URL</label> + <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="key">Application API Key</label> - <input id="key" name="key" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/admin/api] <br /> The key needs all + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="key">Application API Key</label> + <input id="key" name="key" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">[Found at: ptero.example.com/admin/api] <br /> The key needs all Read & Write permissions! </span> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="clientkey">Admin User Client API Key</label> - <input id="clientkey" name="clientkey" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/account/api] <br /> Your Account + <div class="form-group"> + <div class="flex flex-col"> + <label for="clientkey">Admin User Client API Key</label> + <input id="clientkey" name="clientkey" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">[Found at: ptero.example.com/account/api] <br /> Your Account needs to be an Admin!</span> + </div> </div> - </div> - </div> + </div> + </div> + </div> + <div class="w-full flex justify-center "> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero">Submit</button> + </div> + </form> </div> - </div> - <div class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero">Submit</button> - </div> - </form> - </div> <?php @@ -422,44 +422,44 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "First Admin Creation", $subtitle = "Lets create the first admin user!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="createUser"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="createUser"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pteroID">Pterodactyl User ID </label> - <input id="pteroID" name="pteroID" type="text" required value="1" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pteroID">Pterodactyl User ID </label> + <input id="pteroID" name="pteroID" type="text" required value="1" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Password</label> - <input id="pass" name="pass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Password</label> + <input id="pass" name="pass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="repass">Confirm Password</label> - <input id="repass" name="repass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col"> + <label for="repass">Confirm Password</label> + <input id="repass" name="repass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - </div> + </div> - <div class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser">Submit</button> - </div> + <div class="w-full flex justify-center "> + <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser">Submit</button> + </div> - </form> - </div> + </form> + </div> <?php @@ -474,16 +474,16 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "Installation Complete!", $subtitle = "You may navigate to your Dashboard now and log in!"); ?> - <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> - <button class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500">Lets - Go!</button> - </a> + <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center "> + <button class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500">Lets + Go!</button> + </a> - </div> - </div> + </div> + </div> <?php } - ?> + ?> </body> </html> From fd6f0fdc6b0841398d148771b7d1598777a2c28f Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 11 Jun 2024 23:09:22 +0200 Subject: [PATCH 398/514] Revert unnecessary changes in functions.php --- public/install/functions.php | 1 + 1 file changed, 1 insertion(+) diff --git a/public/install/functions.php b/public/install/functions.php index ea1a1af60..ab1fb2601 100644 --- a/public/install/functions.php +++ b/public/install/functions.php @@ -243,6 +243,7 @@ function run_console(string $command, array $descriptors = null, string $cwd = n if ($exit_code > 0) { wh_log('command result: ' . $output, 'error'); throw new Exception("There was an error after running command `$command`", $exit_code); + return $output; } else { return $output; } From d89a805e37731e10c261f7ed61034be2e5999fed Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 11 Jun 2024 23:10:49 +0200 Subject: [PATCH 399/514] Add docker check for mysql host --- public/install/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/install/index.php b/public/install/index.php index 9add8d5ac..e01f47e3a 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -138,7 +138,7 @@ function cardStart($title, $subtitle = null) <div class="form-group"> <div class="flex flex-col mb-3"> <label for="databasehost">Database Host</label> - <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required value="127.0.0.1" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required value="<?php echo (determineIfRunningInDocker() ? 'mysql' : '127.0.0.1')?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> <div class="form-group"> From 03af8b3ed1ef1b644d710bc29b89f5324b7dd744 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 11 Jun 2024 23:50:42 +0200 Subject: [PATCH 400/514] Verify that redis config is valid and working --- public/install/forms.php | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/public/install/forms.php b/public/install/forms.php index 5439ac891..9d0b83e40 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -5,6 +5,7 @@ use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\PHPMailer; +use Predis\Client; require 'phpmailer/Exception.php'; require 'phpmailer/PHPMailer.php'; @@ -69,7 +70,7 @@ wh_log('Feeding the Database successful', 'debug'); header('LOCATION: index.php?step=2.6'); - } catch (\Throwable $th) { + } catch (Throwable $th) { wh_log('Feeding the Database failed', 'error'); header("LOCATION: index.php?step=2.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); } @@ -81,13 +82,27 @@ $redisPort = $_POST['redisport']; $redisPassword = $_POST['redispassword']; - setenv('MEMCACHED_HOST', $redisHost); - setenv('REDIS_HOST', $redisHost); - setenv('REDIS_PORT', $redisPort); - setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); + $redisClient = new Client([ + 'host' => $redisHost, + 'port' => $redisPort, + 'password' => $redisPassword, + 'timeout' => 1.0, + ]); - wh_log('Redis settings set', 'debug'); - header('LOCATION: index.php?step=3'); + try { + $redisClient->ping(); + + setenv('MEMCACHED_HOST', $redisHost); + setenv('REDIS_HOST', $redisHost); + setenv('REDIS_PORT', $redisPort); + setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); + + wh_log('Redis connection successful. Settings updated.', 'debug'); + header('LOCATION: index.php?step=3'); + } catch (Throwable $th) { + wh_log('Redis connection failed. Settings updated.', 'debug'); + header("LOCATION: index.php?step=2.6&message=Please check your credentials!<br>" . $th->getMessage()); + } } if (isset($_POST['checkGeneral'])) { @@ -221,7 +236,7 @@ run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); wh_log('Database updated', 'debug'); header('LOCATION: index.php?step=6'); - } catch (\Throwable $th) { + } catch (Throwable $th) { wh_log("Setting Pterodactyl information failed.", 'error'); header("LOCATION: index.php?step=5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); } @@ -246,7 +261,7 @@ $admin_token = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); wh_log('Database updated', 'debug'); header('LOCATION: index.php?step=6'); - } catch (\Throwable $th) { + } catch (Throwable $th) { wh_log("Getting Pterodactyl information failed.", 'error'); header("LOCATION: index.php?step=5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); } From c508b783ea772eb5178234f8da1f2fa05ecc5132 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Wed, 12 Jun 2024 10:50:05 +0200 Subject: [PATCH 401/514] Fix some error handling on installer --- composer.json | 2 + public/install/forms.php | 139 ++++++++++++++++++++++----------------- public/install/index.php | 16 ++--- 3 files changed, 90 insertions(+), 67 deletions(-) diff --git a/composer.json b/composer.json index b316eec10..f36209e19 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,8 @@ "require": { "php": "^8.1", "ext-intl": "*", + "ext-mysqli": "*", + "ext-curl": "*", "biscolab/laravel-recaptcha": "^5.4", "doctrine/dbal": "^3.5.3", "guzzlehttp/guzzle": "^7.5", diff --git a/public/install/forms.php b/public/install/forms.php index 9d0b83e40..8cbd84e26 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -69,7 +69,7 @@ wh_log($logs, 'debug'); wh_log('Feeding the Database successful', 'debug'); - header('LOCATION: index.php?step=2.6'); + header('LOCATION: index.php?step=3'); } catch (Throwable $th) { wh_log('Feeding the Database failed', 'error'); header("LOCATION: index.php?step=2.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); @@ -98,10 +98,10 @@ setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); wh_log('Redis connection successful. Settings updated.', 'debug'); - header('LOCATION: index.php?step=3'); + header('LOCATION: index.php?step=4'); } catch (Throwable $th) { wh_log('Redis connection failed. Settings updated.', 'debug'); - header("LOCATION: index.php?step=2.6&message=Please check your credentials!<br>" . $th->getMessage()); + header("LOCATION: index.php?step=3&message=Please check your credentials!<br>" . $th->getMessage()); } } @@ -118,7 +118,7 @@ setenv('APP_URL', $appurl); wh_log('App settings set', 'debug'); - header('LOCATION: index.php?step=4'); + header('LOCATION: index.php?step=5'); } if (isset($_POST['checkSMTP'])) { @@ -150,7 +150,7 @@ $mail->send(); } catch (Exception $e) { wh_log($mail->ErrorInfo, 'error'); - header('LOCATION: index.php?step=4&message=Something went wrong while sending test E-Mail!<br>' . $mail->ErrorInfo); + header('LOCATION: index.php?step=5&message=Something went wrong while sending test E-Mail!<br>' . $mail->ErrorInfo); exit(); } @@ -159,7 +159,7 @@ $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); if ($db->connect_error) { wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=4&message=Could not connect to the Database: '); + header('LOCATION: index.php?step=5&message=Could not connect to the Database: '); exit(); } $values = [ @@ -177,7 +177,7 @@ } wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=5'); + header('LOCATION: index.php?step=6'); } if (isset($_POST['checkPtero'])) { @@ -187,10 +187,20 @@ $key = $_POST['key']; $clientkey = $_POST['clientkey']; - if (substr($url, -1) === '/') { - $url = substr_replace($url, '', -1); + $parsedUrl = parse_url($url); + + if (!isset($parsedUrl['scheme'])) { + header('LOCATION: index.php?step=6&message=Please set an URL Scheme like "https://"!'); + exit(); } + if (!isset($parsedUrl['host'])) { + header('LOCATION: index.php?step=6&message=Please set an valid URL host like "https://panel.example.com"!'); + exit(); + } + + $url = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + $callpteroURL = $url . '/api/client/account'; $call = curl_init(); @@ -203,7 +213,7 @@ ]); $callresponse = curl_exec($call); $callresult = json_decode($callresponse, true); - curl_close($call); // Close the connection + curl_close($call); $pteroURL = $url . '/api/application/users'; $ch = curl_init(); @@ -217,38 +227,46 @@ ]); $response = curl_exec($ch); $result = json_decode($response, true); - curl_close($ch); // Close the connection + curl_close($ch); + + if (!is_array($result)) { + wh_log('No array in response found', 'error'); + header('LOCATION: index.php?step=6&message=An unknown Error occured, please try again!'); + } - if (!is_array($result) and $result['errors'][0] !== null) { - header('LOCATION: index.php?step=5&message=Couldn\'t connect to Pterodactyl. Make sure your API key has all read and write permissions!'); + if (array_key_exists('errors', $result) && $result['errors'][0]['detail'] === 'This action is unauthorized.') { wh_log('API CALL ERROR: ' . $result['errors'][0]['code'], 'error'); + header('LOCATION: index.php?step=6&message=Couldn\'t connect to Pterodactyl. Make sure your Application API key has all read and write permissions!'); exit(); - } elseif (!is_array($callresult) and $callresult['errors'][0] !== null or $callresult['attributes']['admin'] == false) { - header('LOCATION: index.php?step=5&message=Your ClientAPI Key is wrong or the account is not an admin!'); + } + + if (array_key_exists('errors', $callresult) && $callresult['errors'][0]['detail'] === 'Unauthenticated.') { wh_log('API CALL ERROR: ' . $callresult['errors'][0]['code'], 'error'); + header('LOCATION: index.php?step=6&message=Your ClientAPI Key is wrong or the account is not an admin!'); + exit(); + } + + try { + run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'"); + run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); + run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); + wh_log('Database updated', 'debug'); + header('LOCATION: index.php?step=7'); + } catch (Throwable $th) { + wh_log("Setting Pterodactyl information failed.", 'error'); + header("LOCATION: index.php?step=6&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); exit(); - } else { - wh_log('Pterodactyl Settings are correct', 'debug'); - - try { - run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'"); - run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); - run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); - wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=6'); - } catch (Throwable $th) { - wh_log("Setting Pterodactyl information failed.", 'error'); - header("LOCATION: index.php?step=5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); - } } } if (isset($_POST['createUser'])) { wh_log('Getting Pterodactyl User', 'debug'); - $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); - if ($db->connect_error) { - wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=6&message=Could not connect to the Database'); + + try { + $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); + } catch (Throwable $th) { + wh_log($th->getMessage(), 'error'); + header('LOCATION: index.php?step=7&message=Could not connect to the Database'); exit(); } @@ -257,36 +275,36 @@ $repass = $_POST['repass']; try { - $panel_url = run_console("php artisan settings:get 'PterodactylSettings' 'panel_url' --sameline"); - $admin_token = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); - wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=6'); + $panelUrl = run_console("php artisan settings:get 'PterodactylSettings' 'panel_url' --sameline"); + $adminToken = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); } catch (Throwable $th) { wh_log("Getting Pterodactyl information failed.", 'error'); - header("LOCATION: index.php?step=5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + header("LOCATION: index.php?step=6&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + exit(); } - $panel_api_url = $panel_url . '/api/application/users/' . $pteroID; + $panelApiUrl = $panelUrl . '/api/application/users/' . $pteroID; $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $panel_api_url); + curl_setopt($ch, CURLOPT_URL, $panelApiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', 'Content-Type: application/json', - 'Authorization: Bearer ' . $admin_token, + 'Authorization: Bearer ' . $adminToken, ]); $response = curl_exec($ch); $result = json_decode($response, true); - curl_close($ch); // Close the connection + curl_close($ch); - if (!$result['attributes']['email']) { - header('LOCATION: index.php?step=6&message=Could not find the user with pterodactyl ID ' . $pteroID); + if ($pass !== $repass) { + header('LOCATION: index.php?step=7&message=The Passwords did not match!'); exit(); } - if ($pass !== $repass) { - header('LOCATION: index.php?step=6&message=The Passwords did not match!'); + + if (array_key_exists('errors', $result)) { + header('LOCATION: index.php?step=7&message=Could not find the user with pterodactyl ID ' . $pteroID); exit(); } @@ -296,12 +314,12 @@ $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $panel_api_url); + curl_setopt($ch, CURLOPT_URL, $panelApiUrl); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json', 'Content-Type: application/json', - 'Authorization: Bearer ' . $admin_token, + 'Authorization: Bearer ' . $adminToken, ]); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'email' => $mail, @@ -312,22 +330,25 @@ ]); $response = curl_exec($ch); $result = json_decode($response, true); - curl_close($ch); // Close the connection - - if (!is_array($result) or in_array($result['errors'][0]['code'], $result)) { - header('LOCATION: index.php?step=5&message=Couldn\'t connect to Pterodactyl. Make sure your API key has all read and write permissions!'); - exit(); - } + curl_close($ch); $random = generateRandomString(); $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; $query2 = "INSERT INTO `" . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\\\Models\\\User', '1')"; - if ($db->query($query1) && $db->query($query2)) { - wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID, 'info'); - header('LOCATION: index.php?step=7'); - } else { - wh_log($db->error, 'error'); - header('LOCATION: index.php?step=6&message=Something went wrong when communicating with the Database'); + try { + $db->query($query1); + $db->query($query2); + + wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID); + header('LOCATION: index.php?step=8'); + } catch (Throwable $th) { + wh_log($th->getMessage(), 'error'); + if (str_contains($th->getMessage(), 'Duplicate entry')) { + header('LOCATION: index.php?step=7&message=User already exists in CtrlPanel\'s Database.'); + } else { + header('LOCATION: index.php?step=7&message=Something went wrong when communicating with the Database.'); + } + exit(); } } diff --git a/public/install/index.php b/public/install/index.php index e01f47e3a..dc3e0448c 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -200,7 +200,7 @@ function cardStart($title, $subtitle = null) } // Redis Config - if (isset($_GET['step']) && $_GET['step'] == 2.6) { + if (isset($_GET['step']) && $_GET['step'] == 3) { echo cardStart($title = "Redis Configuration"); ?> @@ -243,7 +243,7 @@ function cardStart($title, $subtitle = null) } // Dashboard Config - if (isset($_GET['step']) && $_GET['step'] == 3) { + if (isset($_GET['step']) && $_GET['step'] == 4) { echo cardStart($title = "Dashboard Configuration"); ?> @@ -284,7 +284,7 @@ function cardStart($title, $subtitle = null) } // Email Config - if (isset($_GET['step']) && $_GET['step'] == 4) { + if (isset($_GET['step']) && $_GET['step'] == 5) { echo cardStart($title = "E-Mail Configuration", $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!"); ?> @@ -297,7 +297,7 @@ function cardStart($title, $subtitle = null) <div class="col-md-12"> <div class="form-group"> <div class="flex flex-col mb-3"> - <label for="method">Your E-Mail Method</label> + <label for="method">E-Mail Protocol</label> <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> <option value="smtp" selected>SMTP</option> </select> @@ -354,7 +354,7 @@ function cardStart($title, $subtitle = null) <div class="flex w-full justify-around mt-4 gap-8 px-8"> <button type="submit" class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP">Submit</button> - <a href="?step=5" class="w-full"> + <a href="?step=6" class="w-full"> <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600">Skip For Now</button> </a> @@ -367,7 +367,7 @@ function cardStart($title, $subtitle = null) } // Pterodactyl Config - if (isset($_GET['step']) && $_GET['step'] == 5) { + if (isset($_GET['step']) && $_GET['step'] == 6) { echo cardStart($title = "Pterodactyl Configuration", $subtitle = "Lets get some info about your Pterodactyl Installation!"); ?> @@ -418,7 +418,7 @@ function cardStart($title, $subtitle = null) } // Admin Creation Form - if (isset($_GET['step']) && $_GET['step'] == 6) { + if (isset($_GET['step']) && $_GET['step'] == 7) { echo cardStart($title = "First Admin Creation", $subtitle = "Lets create the first admin user!"); ?> @@ -466,7 +466,7 @@ function cardStart($title, $subtitle = null) } // Install Finished - if (isset($_GET['step']) && $_GET['step'] == 7) { + if (isset($_GET['step']) && $_GET['step'] == 8) { $lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); fwrite($lockfile, 'locked'); fclose($lockfile); From 4ccea68f3d724d9200d823b60005a52440bb3f3d Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Wed, 12 Jun 2024 22:21:33 +0200 Subject: [PATCH 402/514] Add set timezone to install script --- public/install/forms.php | 72 +++-- public/install/index.php | 597 ++++++++++++++++++++++----------------- 2 files changed, 376 insertions(+), 293 deletions(-) diff --git a/public/install/forms.php b/public/install/forms.php index 8cbd84e26..23f910efc 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -13,6 +13,16 @@ include 'functions.php'; +if (isset($_POST['timezoneConfig'])) { + wh_log('Setting up Timezone', 'debug'); + $timezone = $_POST['timezone']; + + setenv('APP_TIMEZONE', $timezone); + + wh_log('Timezone set: ' . $timezone, 'debug'); + header('LOCATION: index.php?step=3'); +} + mysqli_report(MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ALL); if (isset($_POST['checkDB'])) { @@ -32,7 +42,7 @@ $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); } catch (mysqli_sql_exception $e) { wh_log($e->getMessage(), 'error'); - header('LOCATION: index.php?step=2&message=' . $e->getMessage()); + header('LOCATION: index.php?step=3&message=' . $e->getMessage()); exit(); } @@ -46,7 +56,7 @@ } wh_log('Database connection successful', 'debug'); - header('LOCATION: index.php?step=2.5'); + header('LOCATION: index.php?step=3.5'); } if (isset($_POST['feedDB'])) { @@ -69,10 +79,10 @@ wh_log($logs, 'debug'); wh_log('Feeding the Database successful', 'debug'); - header('LOCATION: index.php?step=3'); + header('LOCATION: index.php?step=4'); } catch (Throwable $th) { wh_log('Feeding the Database failed', 'error'); - header("LOCATION: index.php?step=2.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + header("LOCATION: index.php?step=3.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); } } @@ -98,10 +108,10 @@ setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); wh_log('Redis connection successful. Settings updated.', 'debug'); - header('LOCATION: index.php?step=4'); + header('LOCATION: index.php?step=5'); } catch (Throwable $th) { wh_log('Redis connection failed. Settings updated.', 'debug'); - header("LOCATION: index.php?step=3&message=Please check your credentials!<br>" . $th->getMessage()); + header("LOCATION: index.php?step=4&message=Please check your credentials!<br>" . $th->getMessage()); } } @@ -110,15 +120,25 @@ $appname = '"' . $_POST['name'] . '"'; $appurl = $_POST['url']; - if (substr($appurl, -1) === '/') { - $appurl = substr_replace($appurl, '', -1); + $parsedUrl = parse_url($appurl); + + if (!isset($parsedUrl['scheme'])) { + header('LOCATION: index.php?step=5&message=Please set an URL Scheme like "https://"!'); + exit(); + } + + if (!isset($parsedUrl['host'])) { + header('LOCATION: index.php?step=5&message=Please set an valid URL host like "https://ctrlpanel.example.com"!'); + exit(); } + $appurl = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + setenv('APP_NAME', $appname); setenv('APP_URL', $appurl); wh_log('App settings set', 'debug'); - header('LOCATION: index.php?step=5'); + header('LOCATION: index.php?step=6'); } if (isset($_POST['checkSMTP'])) { @@ -150,7 +170,7 @@ $mail->send(); } catch (Exception $e) { wh_log($mail->ErrorInfo, 'error'); - header('LOCATION: index.php?step=5&message=Something went wrong while sending test E-Mail!<br>' . $mail->ErrorInfo); + header('LOCATION: index.php?step=6&message=Something went wrong while sending test E-Mail!<br>' . $mail->ErrorInfo); exit(); } @@ -159,7 +179,7 @@ $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); if ($db->connect_error) { wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=5&message=Could not connect to the Database: '); + header('LOCATION: index.php?step=6&message=Could not connect to the Database: '); exit(); } $values = [ @@ -177,7 +197,7 @@ } wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=6'); + header('LOCATION: index.php?step=7'); } if (isset($_POST['checkPtero'])) { @@ -190,12 +210,12 @@ $parsedUrl = parse_url($url); if (!isset($parsedUrl['scheme'])) { - header('LOCATION: index.php?step=6&message=Please set an URL Scheme like "https://"!'); + header('LOCATION: index.php?step=7&message=Please set an URL Scheme like "https://"!'); exit(); } if (!isset($parsedUrl['host'])) { - header('LOCATION: index.php?step=6&message=Please set an valid URL host like "https://panel.example.com"!'); + header('LOCATION: index.php?step=7&message=Please set an valid URL host like "https://panel.example.com"!'); exit(); } @@ -231,18 +251,18 @@ if (!is_array($result)) { wh_log('No array in response found', 'error'); - header('LOCATION: index.php?step=6&message=An unknown Error occured, please try again!'); + header('LOCATION: index.php?step=7&message=An unknown Error occured, please try again!'); } if (array_key_exists('errors', $result) && $result['errors'][0]['detail'] === 'This action is unauthorized.') { wh_log('API CALL ERROR: ' . $result['errors'][0]['code'], 'error'); - header('LOCATION: index.php?step=6&message=Couldn\'t connect to Pterodactyl. Make sure your Application API key has all read and write permissions!'); + header('LOCATION: index.php?step=7&message=Couldn\'t connect to Pterodactyl. Make sure your Application API key has all read and write permissions!'); exit(); } if (array_key_exists('errors', $callresult) && $callresult['errors'][0]['detail'] === 'Unauthenticated.') { wh_log('API CALL ERROR: ' . $callresult['errors'][0]['code'], 'error'); - header('LOCATION: index.php?step=6&message=Your ClientAPI Key is wrong or the account is not an admin!'); + header('LOCATION: index.php?step=7&message=Your ClientAPI Key is wrong or the account is not an admin!'); exit(); } @@ -251,10 +271,10 @@ run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=7'); + header('LOCATION: index.php?step=8'); } catch (Throwable $th) { wh_log("Setting Pterodactyl information failed.", 'error'); - header("LOCATION: index.php?step=6&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + header("LOCATION: index.php?step=7&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); exit(); } } @@ -266,7 +286,7 @@ $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); } catch (Throwable $th) { wh_log($th->getMessage(), 'error'); - header('LOCATION: index.php?step=7&message=Could not connect to the Database'); + header('LOCATION: index.php?step=8&message=Could not connect to the Database'); exit(); } @@ -279,7 +299,7 @@ $adminToken = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); } catch (Throwable $th) { wh_log("Getting Pterodactyl information failed.", 'error'); - header("LOCATION: index.php?step=6&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + header("LOCATION: index.php?step=7&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); exit(); } @@ -299,12 +319,12 @@ curl_close($ch); if ($pass !== $repass) { - header('LOCATION: index.php?step=7&message=The Passwords did not match!'); + header('LOCATION: index.php?step=8&message=The Passwords did not match!'); exit(); } if (array_key_exists('errors', $result)) { - header('LOCATION: index.php?step=7&message=Could not find the user with pterodactyl ID ' . $pteroID); + header('LOCATION: index.php?step=8&message=Could not find the user with pterodactyl ID ' . $pteroID); exit(); } @@ -341,13 +361,13 @@ $db->query($query2); wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID); - header('LOCATION: index.php?step=8'); + header('LOCATION: index.php?step=9'); } catch (Throwable $th) { wh_log($th->getMessage(), 'error'); if (str_contains($th->getMessage(), 'Duplicate entry')) { - header('LOCATION: index.php?step=7&message=User already exists in CtrlPanel\'s Database.'); + header('LOCATION: index.php?step=8&message=User already exists in CtrlPanel\'s Database.'); } else { - header('LOCATION: index.php?step=7&message=Something went wrong when communicating with the Database.'); + header('LOCATION: index.php?step=8&message=Something went wrong when communicating with the Database.'); } exit(); } diff --git a/public/install/index.php b/public/install/index.php index dc3e0448c..78656d380 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -5,7 +5,7 @@ exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } -function cardStart($title, $subtitle = null) +function cardStart($title, $subtitle = null): string { return " <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> @@ -14,13 +14,12 @@ function cardStart($title, $subtitle = null) <h2 class='text-xl text-center mb-2'>$title</h2>" . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); } -?> -<html> +?> +<html lang="en"> <head> <title>CtrlPanel.gg installer Script</title> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="/install/styles.css" rel="stylesheet"> <style> @@ -63,145 +62,197 @@ function cardStart($title, $subtitle = null) <body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> - <?php +<?php - // Getting started - if (!isset($_GET['step']) || $_GET['step'] == 1) { +// Getting started +if (!isset($_GET['step']) || $_GET['step'] == 1) { ?> - <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> + <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> - <ul class="list-none mb-2"> + <ul class="list-none mb-2"> - <li class="<?php echo checkHTTPS() == true ? 'ok' : 'not-ok'; ?> check">HTTPS is required</li> + <li class="<?php echo checkHTTPS() == true ? 'ok' : 'not-ok'; ?> check">HTTPS is required</li> - <li class="<?php echo checkWriteable() == true ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> + <li class="<?php echo checkWriteable() == true ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> - <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) - </li> + <li class="<?php echo checkPhpVersion() == 'OK' ? 'ok' : 'not-ok'; ?> check"> + php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) + </li> - <li class="<?php echo getMySQLVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - mysql version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>) - </li> + <li class="<?php echo getMySQLVersion() == 'OK' ? 'ok' : 'not-ok'; ?> check"> + mysql version: <?php echo getMySQLVersion(); ?> (minimum required <?php echo $requirements['mysql']; ?>) + </li> - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> - Missing php-extentions: - <?php echo count(checkExtensions()) == 0 ? 'none' : ''; - foreach (checkExtensions() as $ext) { - echo $ext . ', '; - } - echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?> - </li> + <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> + Missing php-extentions: + <?php echo count(checkExtensions()) == 0 ? 'none' : ''; + foreach (checkExtensions() as $ext) { + echo $ext . ', '; + } + echo count(checkExtensions()) == 0 ? '' : '(Proceed anyway)'; ?> + </li> - <!-- <li class="<?php echo getZipVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> Zip - version: <?php echo getZipVersion(); ?> </li> --> + <!-- <li class="<?php echo getZipVersion() == 'OK' ? 'ok' : 'not-ok'; ?> check"> Zip + version: <?php echo getZipVersion(); ?> </li> --> - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Git version: - <?php echo getGitVersion(); ?> - </li> + <li class="<?php echo getGitVersion() == 'OK' ? 'ok' : 'not-ok'; ?> check"> + Git version: + <?php echo getGitVersion(); ?> + </li> - <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Tar version: - <?php echo getTarVersion(); ?> - </li> - </ul> + <li class="<?php echo getTarVersion() == 'OK' ? 'ok' : 'not-ok'; ?> check"> + Tar version: + <?php echo getTarVersion(); ?> + </li> + </ul> - </div> - <a href="?step=2" class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500">Lets - go</button> - </a> + <a href="?step=2" class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> + Lets go! + </button> + </a> <?php - } +} - // DB Config - if (isset($_GET['step']) && $_GET['step'] == 2) { +// Timezone Config +if (isset($_GET['step']) && $_GET['step'] == 2) { + echo cardStart($title = "Timezone Configuration"); ?> - echo cardStart($title = "Database Configuration"); ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="timezoneConfig"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="timezone">Timezone</label> + <select id="timezone" name="timezone" required + class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <?php + $timezoneIdentifiers = DateTimeZone::listIdentifiers(); + foreach ($timezoneIdentifiers as $timezoneIdentifier) { + if ($timezoneIdentifier == 'UTC') { + continue; + } + + echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; + } ?> + </select> + </div> + </div> + </div> + </div> - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasedriver">Database Driver</label> - <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required value="mysql" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="w-full flex justify-center "> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="timezoneConfig">Submit + </button> + </div> + </form> + <?php +} + +// DB Config +if (isset($_GET['step']) && $_GET['step'] == 3) { + echo cardStart($title = "Database Configuration"); ?> + + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasedriver">Database Driver</label> + <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required + value="mysql" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasehost">Database Host</label> - <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required value="<?php echo (determineIfRunningInDocker() ? 'mysql' : '127.0.0.1')?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasehost">Database Host</label> + <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required + value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseport">Database Port</label> - <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required value="3306" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseport">Database Port</label> + <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required + value="3306" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required value="ctrlpaneluser" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuser">Database User</label> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required + value="ctrlpaneluser" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuserpass">Database User Password</label> - <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" required class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none "> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuserpass">Database User Password</label> + <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" + required + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none "> </div> + </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="database">Database</label> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - </div> - </div> + </div> - </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB">Submit</button> - </div> - </form> + <div class="w-full flex justify-center "> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkDB">Submit + </button> </div> + </form> <?php - } +} - // DB Migration & APP_KEY Generation - if (isset($_GET['step']) && $_GET['step'] == 2.5) { ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> +// DB Migration & APP_KEY Generation +if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> - <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> + <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB">Submit</button> - </div> - </form> - <?php + <div class="w-full flex justify-center "> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="feedDB">Submit + </button> + </div> + </form> + <?php } // Redis Config - if (isset($_GET['step']) && $_GET['step'] == 3) { - + if (isset($_GET['step']) && $_GET['step'] == 4) { echo cardStart($title = "Redis Configuration"); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="redisSetup"> @@ -214,259 +265,272 @@ function cardStart($title, $subtitle = null) <div class="form-group"> <div class="flex flex-col mb-3"> <label for="redishost">Redis Host</label> - <input x-model="redishost" id="redishost" name="redishost" type="text" required value="<?php echo (determineIfRunningInDocker() ? 'redis' : '127.0.0.1')?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input x-model="redishost" id="redishost" name="redishost" type="text" required + value="<?php echo(determineIfRunningInDocker() ? 'redis' : '127.0.0.1') ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> <div class="form-group"> <div class="flex flex-col mb-3"> <label for="redisport">Redis Port</label> - <input x-model="redisport" id="redisport" name="redisport" type="number" required value="6379" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input x-model="redisport" id="redisport" name="redisport" type="number" required + value="6379" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> <div class="form-group"> <div class="flex flex-col mb-3"> <label for="redispassword">Redis Password (optionally, only if configured)</label> - <input x-model="redispassword" id="redispassword" name="redispassword" type="text" placeholder="usually can be left blank" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <input x-model="redispassword" id="redispassword" name="redispassword" type="text" + placeholder="usually can be left blank" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> </div> </div> - </div> <div class="w-full flex justify-center"> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="redisSetup">Submit</button> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="redisSetup">Submit + </button> </div> </form> - </div> <?php } // Dashboard Config - if (isset($_GET['step']) && $_GET['step'] == 4) { - + if (isset($_GET['step']) && $_GET['step'] == 5) { echo cardStart($title = "Dashboard Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkGeneral"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkGeneral"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="database">Dashboard URL</label> - <input id="url" name="url" type="text" required value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="database">Dashboard URL</label> + <input id="url" name="url" type="text" required + value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="name">Dashboard Name</label> - <input id="name" name="name" type="text" required value="CtrlPanel" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="name">Dashboard Name</label> + <input id="name" name="name" type="text" required value="CtrlPanel" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - </div> - </div> </div> + </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral">Submit</button> - </div> - </form> + <div class="w-full flex justify-center "> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkGeneral">Submit + </button> </div> + </form> <?php } // Email Config - if (isset($_GET['step']) && $_GET['step'] == 5) { - + if (isset($_GET['step']) && $_GET['step'] == 6) { echo cardStart($title = "E-Mail Configuration", $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkSMTP"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="method">E-Mail Protocol</label> - <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="smtp" selected>SMTP</option> - </select> - </div> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkSMTP"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="method">E-Mail Protocol</label> + <select id="method" name="method" required + class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="smtp" selected>SMTP</option> + </select> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="host">Your Mailer-Host</label> - <input id="host" name="host" type="text" required value="smtp.google.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="host">Your Mailer-Host</label> + <input id="host" name="host" type="text" required value="smtp.google.com" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="port">Your Mail Port</label> - <input id="port" name="port" type="number" required value="567" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="port">Your Mail Port</label> + <input id="port" name="port" type="number" required value="567" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="user">Your Mail User</label> - <input id="user" name="user" type="text" required value="info@mydomain.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="user">Your Mail User</label> + <input id="user" name="user" type="text" required value="info@mydomain.com" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Your Mail-User Password</label> - <input id="pass" name="pass" type="password" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Your Mail-User Password</label> + <input id="pass" name="pass" type="password" required value="" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="encryption">Your Mail encryption method</label> - <select id="encryption" name="encryption" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="tls" selected>TLS</option> - <option value="ssl">SSL</option> - <option value="null">None</option> - </select> - </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="encryption">Your Mail encryption method</label> + <select id="encryption" name="encryption" required + class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="tls" selected>TLS</option> + <option value="ssl">SSL</option> + <option value="null">None</option> + </select> </div> - </div> - - - </div> + </div> - </div> - <div class="flex w-full justify-around mt-4 gap-8 px-8"> - <button type="submit" class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP">Submit</button> + <div class="flex w-full justify-around mt-4 gap-8 px-8"> + <button type="submit" + class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkSMTP">Submit + </button> - <a href="?step=6" class="w-full"> - <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600">Skip - For Now</button> - </a> - </div> - </form> + <a href="?step=7" class="w-full"> + <button type="button" + class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600"> + Skip For Now + </button> + </a> </div> + </form> <?php } // Pterodactyl Config - if (isset($_GET['step']) && $_GET['step'] == 6) { - + if (isset($_GET['step']) && $_GET['step'] == 7) { echo cardStart($title = "Pterodactyl Configuration", $subtitle = "Lets get some info about your Pterodactyl Installation!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkPtero"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkPtero"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> - <label for="url">Pterodactyl URL</label> - <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <label for="url">Pterodactyl URL</label> + <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="key">Application API Key</label> - <input id="key" name="key" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/admin/api] <br /> The key needs all - Read & Write permissions! </span> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="key">Application API Key</label> + <input id="key" name="key" type="text" required value="" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">[Found at: ptero.example.com/admin/api] <br/> The key needs all + Read & Write permissions! </span> </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="clientkey">Admin User Client API Key</label> - <input id="clientkey" name="clientkey" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/account/api] <br /> Your Account - needs to be an Admin!</span> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="clientkey">Admin User Client API Key</label> + <input id="clientkey" name="clientkey" type="text" required value="" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">[Found at: ptero.example.com/account/api] <br/> Your Account + needs to be an Admin!</span> </div> - - </div> - - </div> - </div> - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero">Submit</button> </div> - </form> </div> + <div class="w-full flex justify-center "> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkPtero">Submit + </button> + </div> + </form> <?php } // Admin Creation Form - if (isset($_GET['step']) && $_GET['step'] == 7) { - + if (isset($_GET['step']) && $_GET['step'] == 8) { echo cardStart($title = "First Admin Creation", $subtitle = "Lets create the first admin user!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="createUser"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="createUser"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pteroID">Pterodactyl User ID </label> - <input id="pteroID" name="pteroID" type="text" required value="1" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Password</label> - <input id="pass" name="pass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="repass">Confirm Password</label> - <input id="repass" name="repass" type="password" required value="" minlength="8" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pteroID">Pterodactyl User ID </label> + <input id="pteroID" name="pteroID" type="text" required value="1" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Password</label> + <input id="pass" name="pass" type="password" required value="" minlength="8" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> </div> - - - <div class="w-full flex justify-center "> - <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser">Submit</button> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="repass">Confirm Password</label> + <input id="repass" name="repass" type="password" required value="" minlength="8" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - </form> + <div class="w-full flex justify-center "> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="createUser">Submit + </button> </div> + </form> <?php } // Install Finished - if (isset($_GET['step']) && $_GET['step'] == 8) { + if (isset($_GET['step']) && $_GET['step'] == 9) { $lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); fwrite($lockfile, 'locked'); fclose($lockfile); @@ -474,16 +538,15 @@ function cardStart($title, $subtitle = null) echo cardStart($title = "Installation Complete!", $subtitle = "You may navigate to your Dashboard now and log in!"); ?> - <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center "> - <button class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500">Lets - Go!</button> - </a> + <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center "> + <button + class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500"> + Lets Go! + </button> + </a> - </div> - </div> <?php } - ?> + ?> </body> - </html> From 3136e185db567eda8161ddbb91c408ec27ca5d89 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Wed, 12 Jun 2024 22:41:10 +0200 Subject: [PATCH 403/514] Remove unnecessary variable --- public/install/index.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/install/index.php b/public/install/index.php index 1ee6ddc5a..7c7765c8b 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -134,8 +134,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s <select id="timezone" name="timezone" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> <?php - $timezoneIdentifiers = DateTimeZone::listIdentifiers(); - foreach ($timezoneIdentifiers as $timezoneIdentifier) { + foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { if ($timezoneIdentifier == 'UTC') { continue; } From effc8acad0a3b0b735fa1befbe209fb50d7f4ece Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Wed, 12 Jun 2024 22:51:35 +0200 Subject: [PATCH 404/514] Remove unnecessary whitespaces --- public/install/index.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/public/install/index.php b/public/install/index.php index 7c7765c8b..535411e45 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -135,7 +135,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> <?php foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { - if ($timezoneIdentifier == 'UTC') { + if ($timezoneIdentifier === 'UTC') { continue; } @@ -147,7 +147,7 @@ class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center "> + <div class="w-full flex justify-center"> <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="timezoneConfig">Submit @@ -205,7 +205,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m <label for="databaseuserpass">Database User Password</label> <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" required - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none "> + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> @@ -219,7 +219,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center "> + <div class="w-full flex justify-center"> <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB">Submit @@ -240,7 +240,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> - <div class="w-full flex justify-center "> + <div class="w-full flex justify-center"> <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB">Submit @@ -330,7 +330,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center "> + <div class="w-full flex justify-center"> <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral">Submit @@ -468,7 +468,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> </div> - <div class="w-full flex justify-center "> + <div class="w-full flex justify-center"> <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero">Submit @@ -516,7 +516,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center "> + <div class="w-full flex justify-center"> <button class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser">Submit @@ -537,7 +537,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s echo cardStart($title = "Installation Complete!", $subtitle = "You may navigate to your Dashboard now and log in!"); ?> - <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center "> + <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> <button class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500"> Lets Go! From b027456b8b5589d08547046d0b36fb57fab01bf9 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 10:17:53 +0200 Subject: [PATCH 405/514] refactor: renamed install into installer and started to split the files --- app/Http/Middleware/InstallerLock.php | 2 +- public/{install => installer}/dotenv.php | 0 public/{install => installer}/forms.php | 0 public/{install => installer}/functions.php | 0 public/{install => installer}/index.php | 22 +++++++++---------- .../phpmailer/Exception.php | 0 .../phpmailer/PHPMailer.php | 0 .../{install => installer}/phpmailer/SMTP.php | 0 public/installer/src/forms/admin.php | 0 public/installer/src/forms/dashboard.php | 0 public/installer/src/forms/database.php | 0 public/installer/src/forms/pterodactyl.php | 0 public/installer/src/forms/redis.php | 0 public/installer/src/forms/smtp.php | 0 public/installer/src/forms/timezone.php | 0 public/installer/src/functions/database.php | 0 .../installer/src/functions/environment.php | 0 public/installer/src/functions/logging.php | 0 public/installer/src/functions/shell.php | 0 public/installer/src/functions/utils.php | 0 public/{install => installer}/styles.css | 0 .../{install => installer}/tailwind.config.js | 0 .../tailwind_styles.css | 0 public/installer/view/admin-creation.php | 0 .../view/dashboard-configuration.php | 0 .../installer/view/database-configuration.php | 0 public/installer/view/database-migration.php | 0 public/installer/view/email-configuration.php | 0 .../installer/view/installation-complete.php | 0 public/installer/view/mandatory-checks.php | 0 .../view/pterodactyl-configuration.php | 0 public/installer/view/redis-configuration.php | 0 .../installer/view/timezone-configuration.php | 0 33 files changed, 12 insertions(+), 12 deletions(-) rename public/{install => installer}/dotenv.php (100%) rename public/{install => installer}/forms.php (100%) rename public/{install => installer}/functions.php (100%) rename public/{install => installer}/index.php (97%) rename public/{install => installer}/phpmailer/Exception.php (100%) rename public/{install => installer}/phpmailer/PHPMailer.php (100%) rename public/{install => installer}/phpmailer/SMTP.php (100%) create mode 100644 public/installer/src/forms/admin.php create mode 100644 public/installer/src/forms/dashboard.php create mode 100644 public/installer/src/forms/database.php create mode 100644 public/installer/src/forms/pterodactyl.php create mode 100644 public/installer/src/forms/redis.php create mode 100644 public/installer/src/forms/smtp.php create mode 100644 public/installer/src/forms/timezone.php create mode 100644 public/installer/src/functions/database.php create mode 100644 public/installer/src/functions/environment.php create mode 100644 public/installer/src/functions/logging.php create mode 100644 public/installer/src/functions/shell.php create mode 100644 public/installer/src/functions/utils.php rename public/{install => installer}/styles.css (100%) rename public/{install => installer}/tailwind.config.js (100%) rename public/{install => installer}/tailwind_styles.css (100%) create mode 100644 public/installer/view/admin-creation.php create mode 100644 public/installer/view/dashboard-configuration.php create mode 100644 public/installer/view/database-configuration.php create mode 100644 public/installer/view/database-migration.php create mode 100644 public/installer/view/email-configuration.php create mode 100644 public/installer/view/installation-complete.php create mode 100644 public/installer/view/mandatory-checks.php create mode 100644 public/installer/view/pterodactyl-configuration.php create mode 100644 public/installer/view/redis-configuration.php create mode 100644 public/installer/view/timezone-configuration.php diff --git a/app/Http/Middleware/InstallerLock.php b/app/Http/Middleware/InstallerLock.php index 9390a598c..f6f09e3b5 100644 --- a/app/Http/Middleware/InstallerLock.php +++ b/app/Http/Middleware/InstallerLock.php @@ -17,7 +17,7 @@ class InstallerLock public function handle(Request $request, Closure $next) { if (!file_exists(base_path()."/install.lock")){ - return redirect('/install'); + return redirect('/installer'); } return $next($request); } diff --git a/public/install/dotenv.php b/public/installer/dotenv.php similarity index 100% rename from public/install/dotenv.php rename to public/installer/dotenv.php diff --git a/public/install/forms.php b/public/installer/forms.php similarity index 100% rename from public/install/forms.php rename to public/installer/forms.php diff --git a/public/install/functions.php b/public/installer/functions.php similarity index 100% rename from public/install/functions.php rename to public/installer/functions.php diff --git a/public/install/index.php b/public/installer/index.php similarity index 97% rename from public/install/index.php rename to public/installer/index.php index 535411e45..5d73c2e2a 100644 --- a/public/install/index.php +++ b/public/installer/index.php @@ -1,7 +1,7 @@ <?php include 'functions.php'; -if (file_exists('../../install.lock')) { +if (file_exists('../../installer.lock')) { exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } @@ -21,7 +21,7 @@ function cardStart($title, $subtitle = null): string <head> <title>CtrlPanel.gg installer Script</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link href="/install/styles.css" rel="stylesheet"> + <link href="/installer/styles.css" rel="stylesheet"> <style> body { color-scheme: dark; @@ -121,7 +121,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 2) { echo cardStart($title = "Timezone Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="timezoneConfig"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="timezoneConfig"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -161,7 +161,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 3) { echo cardStart($title = "Database Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkDB"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -234,7 +234,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="feedDB"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; @@ -254,7 +254,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 4) { echo cardStart($title = "Redis Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="redisSetup"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="redisSetup"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -303,7 +303,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 5) { echo cardStart($title = "Dashboard Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkGeneral"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkGeneral"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; @@ -346,7 +346,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 6) { echo cardStart($title = "E-Mail Configuration", $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkSMTP"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkSMTP"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -433,7 +433,7 @@ class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-60 if (isset($_GET['step']) && $_GET['step'] == 7) { echo cardStart($title = "Pterodactyl Configuration", $subtitle = "Lets get some info about your Pterodactyl Installation!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkPtero"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkPtero"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -484,7 +484,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 8) { echo cardStart($title = "First Admin Creation", $subtitle = "Lets create the first admin user!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="createUser"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="createUser"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; @@ -530,7 +530,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s // Install Finished if (isset($_GET['step']) && $_GET['step'] == 9) { - $lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); + $lockfile = fopen('../../installer.lock', 'w') or exit('Unable to open file!'); fwrite($lockfile, 'locked'); fclose($lockfile); diff --git a/public/install/phpmailer/Exception.php b/public/installer/phpmailer/Exception.php similarity index 100% rename from public/install/phpmailer/Exception.php rename to public/installer/phpmailer/Exception.php diff --git a/public/install/phpmailer/PHPMailer.php b/public/installer/phpmailer/PHPMailer.php similarity index 100% rename from public/install/phpmailer/PHPMailer.php rename to public/installer/phpmailer/PHPMailer.php diff --git a/public/install/phpmailer/SMTP.php b/public/installer/phpmailer/SMTP.php similarity index 100% rename from public/install/phpmailer/SMTP.php rename to public/installer/phpmailer/SMTP.php diff --git a/public/installer/src/forms/admin.php b/public/installer/src/forms/admin.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/dashboard.php b/public/installer/src/forms/dashboard.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/pterodactyl.php b/public/installer/src/forms/pterodactyl.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/redis.php b/public/installer/src/forms/redis.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/smtp.php b/public/installer/src/forms/smtp.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/timezone.php b/public/installer/src/forms/timezone.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/database.php b/public/installer/src/functions/database.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/environment.php b/public/installer/src/functions/environment.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/logging.php b/public/installer/src/functions/logging.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/shell.php b/public/installer/src/functions/shell.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/utils.php b/public/installer/src/functions/utils.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/install/styles.css b/public/installer/styles.css similarity index 100% rename from public/install/styles.css rename to public/installer/styles.css diff --git a/public/install/tailwind.config.js b/public/installer/tailwind.config.js similarity index 100% rename from public/install/tailwind.config.js rename to public/installer/tailwind.config.js diff --git a/public/install/tailwind_styles.css b/public/installer/tailwind_styles.css similarity index 100% rename from public/install/tailwind_styles.css rename to public/installer/tailwind_styles.css diff --git a/public/installer/view/admin-creation.php b/public/installer/view/admin-creation.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/dashboard-configuration.php b/public/installer/view/dashboard-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/database-configuration.php b/public/installer/view/database-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/database-migration.php b/public/installer/view/database-migration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/email-configuration.php b/public/installer/view/email-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/installation-complete.php b/public/installer/view/installation-complete.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/mandatory-checks.php b/public/installer/view/mandatory-checks.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/pterodactyl-configuration.php b/public/installer/view/pterodactyl-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/redis-configuration.php b/public/installer/view/redis-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/timezone-configuration.php b/public/installer/view/timezone-configuration.php new file mode 100644 index 000000000..e69de29bb From 5e2a3d74ef11e5f4f25a1bc2d5d6561651758ea6 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 10:26:48 +0200 Subject: [PATCH 406/514] fix: renamed view to views folder in installer --- public/installer/{view => views}/admin-creation.php | 0 public/installer/{view => views}/dashboard-configuration.php | 0 public/installer/{view => views}/database-configuration.php | 0 public/installer/{view => views}/database-migration.php | 0 public/installer/{view => views}/email-configuration.php | 0 public/installer/{view => views}/installation-complete.php | 0 public/installer/{view => views}/mandatory-checks.php | 0 public/installer/{view => views}/pterodactyl-configuration.php | 0 public/installer/{view => views}/redis-configuration.php | 0 public/installer/{view => views}/timezone-configuration.php | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename public/installer/{view => views}/admin-creation.php (100%) rename public/installer/{view => views}/dashboard-configuration.php (100%) rename public/installer/{view => views}/database-configuration.php (100%) rename public/installer/{view => views}/database-migration.php (100%) rename public/installer/{view => views}/email-configuration.php (100%) rename public/installer/{view => views}/installation-complete.php (100%) rename public/installer/{view => views}/mandatory-checks.php (100%) rename public/installer/{view => views}/pterodactyl-configuration.php (100%) rename public/installer/{view => views}/redis-configuration.php (100%) rename public/installer/{view => views}/timezone-configuration.php (100%) diff --git a/public/installer/view/admin-creation.php b/public/installer/views/admin-creation.php similarity index 100% rename from public/installer/view/admin-creation.php rename to public/installer/views/admin-creation.php diff --git a/public/installer/view/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php similarity index 100% rename from public/installer/view/dashboard-configuration.php rename to public/installer/views/dashboard-configuration.php diff --git a/public/installer/view/database-configuration.php b/public/installer/views/database-configuration.php similarity index 100% rename from public/installer/view/database-configuration.php rename to public/installer/views/database-configuration.php diff --git a/public/installer/view/database-migration.php b/public/installer/views/database-migration.php similarity index 100% rename from public/installer/view/database-migration.php rename to public/installer/views/database-migration.php diff --git a/public/installer/view/email-configuration.php b/public/installer/views/email-configuration.php similarity index 100% rename from public/installer/view/email-configuration.php rename to public/installer/views/email-configuration.php diff --git a/public/installer/view/installation-complete.php b/public/installer/views/installation-complete.php similarity index 100% rename from public/installer/view/installation-complete.php rename to public/installer/views/installation-complete.php diff --git a/public/installer/view/mandatory-checks.php b/public/installer/views/mandatory-checks.php similarity index 100% rename from public/installer/view/mandatory-checks.php rename to public/installer/views/mandatory-checks.php diff --git a/public/installer/view/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php similarity index 100% rename from public/installer/view/pterodactyl-configuration.php rename to public/installer/views/pterodactyl-configuration.php diff --git a/public/installer/view/redis-configuration.php b/public/installer/views/redis-configuration.php similarity index 100% rename from public/installer/view/redis-configuration.php rename to public/installer/views/redis-configuration.php diff --git a/public/installer/view/timezone-configuration.php b/public/installer/views/timezone-configuration.php similarity index 100% rename from public/installer/view/timezone-configuration.php rename to public/installer/views/timezone-configuration.php From ccf64c90054d04b09116bc92366e4ef027b0a86d Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 11:16:27 +0200 Subject: [PATCH 407/514] refactor: moved mailer files of installer in src --- public/installer/{ => src}/phpmailer/Exception.php | 0 public/installer/{ => src}/phpmailer/PHPMailer.php | 0 public/installer/{ => src}/phpmailer/SMTP.php | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename public/installer/{ => src}/phpmailer/Exception.php (100%) rename public/installer/{ => src}/phpmailer/PHPMailer.php (100%) rename public/installer/{ => src}/phpmailer/SMTP.php (100%) diff --git a/public/installer/phpmailer/Exception.php b/public/installer/src/phpmailer/Exception.php similarity index 100% rename from public/installer/phpmailer/Exception.php rename to public/installer/src/phpmailer/Exception.php diff --git a/public/installer/phpmailer/PHPMailer.php b/public/installer/src/phpmailer/PHPMailer.php similarity index 100% rename from public/installer/phpmailer/PHPMailer.php rename to public/installer/src/phpmailer/PHPMailer.php diff --git a/public/installer/phpmailer/SMTP.php b/public/installer/src/phpmailer/SMTP.php similarity index 100% rename from public/installer/phpmailer/SMTP.php rename to public/installer/src/phpmailer/SMTP.php From 37ec601ed3df38f0b08539b4859e1eca2c1d2599 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 11:16:53 +0200 Subject: [PATCH 408/514] refactor: refactored views of installer --- config/database.php | 2 +- public/installer/forms.php | 8 +- public/installer/index.php | 588 ++---------------- public/installer/views/admin-creation.php | 48 ++ .../views/dashboard-configuration.php | 42 ++ .../views/database-configuration.php | 75 +++ public/installer/views/database-migration.php | 23 + .../installer/views/email-configuration.php | 80 +++ .../installer/views/installation-complete.php | 22 + public/installer/views/layout-bottom.php | 7 + public/installer/views/layout-top.php | 59 ++ public/installer/views/mandatory-checks.php | 54 ++ .../views/pterodactyl-configuration.php | 52 ++ .../installer/views/redis-configuration.php | 51 ++ .../views/timezone-configuration.php | 42 ++ 15 files changed, 601 insertions(+), 552 deletions(-) create mode 100644 public/installer/views/layout-bottom.php create mode 100644 public/installer/views/layout-top.php diff --git a/config/database.php b/config/database.php index dc722b5fd..1917bc6a7 100644 --- a/config/database.php +++ b/config/database.php @@ -119,7 +119,7 @@ 'redis' => [ - 'client' => env('REDIS_CLIENT', 'phpredis'), + 'client' => env('REDIS_CLIENT', 'predis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), diff --git a/public/installer/forms.php b/public/installer/forms.php index 23f910efc..af5fba754 100644 --- a/public/installer/forms.php +++ b/public/installer/forms.php @@ -7,11 +7,9 @@ use PHPMailer\PHPMailer\PHPMailer; use Predis\Client; -require 'phpmailer/Exception.php'; -require 'phpmailer/PHPMailer.php'; -require 'phpmailer/SMTP.php'; - -include 'functions.php'; +require './src/phpmailer/Exception.php'; +require './src/phpmailer/PHPMailer.php'; +require './src/phpmailer/SMTP.php'; if (isset($_POST['timezoneConfig'])) { wh_log('Setting up Timezone', 'debug'); diff --git a/public/installer/index.php b/public/installer/index.php index 5d73c2e2a..24ee47bbd 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -1,551 +1,47 @@ <?php -include 'functions.php'; -if (file_exists('../../installer.lock')) { +// Include the function files +require_once './src/functions/environment.php'; +require_once './src/functions/database.php'; +require_once './src/functions/shell.php'; +require_once './src/functions/logging.php'; +require_once './src/functions/utils.php'; + +// Include the form files +require_once './src/forms/timezone.php'; +require_once './src/forms/database.php'; +require_once './src/forms/redis.php'; +require_once './src/forms/dashboard.php'; +require_once './src/forms/smtp.php'; +require_once './src/forms/pterodactyl.php'; +require_once './src/forms/admin.php'; + +require_once './functions.php'; +require_once './forms.php'; + +if (file_exists('../../install.lock')) { exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } -function cardStart($title, $subtitle = null): string -{ - return " - <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> - <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> - <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> - <h2 class='text-xl text-center mb-2'>$title</h2>" - . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); -} - -?> - -<html lang="en"> -<head> - <title>CtrlPanel.gg installer Script</title> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link href="/installer/styles.css" rel="stylesheet"> - <style> - body { - color-scheme: dark; - } - - .check { - display: flex; - gap: 5px; - align-items: center; - margin-bottom: 5px; - } - - .check::before { - width: 20px; - height: 20px; - display: block; - } - - .ok { - color: lightgreen; - } - - /* Green Checkmark */ - .ok::before { - content: url("data:image/svg+xml,%3Csvg fill='none' stroke='lightgreen' stroke-width='1.5' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); - } - - .not-ok { - color: lightcoral; - } - - /* Red Cross */ - .not-ok::before { - content: url("data:image/svg+xml,%3Csvg fill='none' stroke='lightcoral' stroke-width='1.5' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); - } - </style> -</head> - -<body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> - -<?php - -// Getting started -if (!isset($_GET['step']) || $_GET['step'] == 1) { - ?> - <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> - - <ul class="list-none mb-2"> - - <li class="<?php echo checkWriteable() ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> - - <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) - </li> - - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> - Missing php-extentions: - <?php echo count(checkExtensions()) == 0 ? 'none' : ''; - foreach (checkExtensions() as $ext) { - echo $ext . ', '; - } - echo count(checkExtensions()) === 0 ? '' : '(Proceed anyway)'; ?> - </li> - - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Git version: - <?php echo getGitVersion(); ?> - </li> - - <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Tar version: - <?php echo getTarVersion(); ?> - </li> - - <li> - <p class="text-neutral-400 mb-1"> - <br> - <span style="color: #eab308;">Important:</span> - CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> - Please make sure you have these installed and running before you continue. - </p> - </li> - - </ul> - - <a href="?step=2" class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> - Lets go! - </button> - </a> - - <?php -} - -// Timezone Config -if (isset($_GET['step']) && $_GET['step'] == 2) { - echo cardStart($title = "Timezone Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="timezoneConfig"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="timezone">Timezone</label> - <select id="timezone" name="timezone" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <?php - foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { - if ($timezoneIdentifier === 'UTC') { - continue; - } - - echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; - } ?> - </select> - </div> - </div> - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="timezoneConfig">Submit - </button> - </div> - </form> - <?php -} - -// DB Config -if (isset($_GET['step']) && $_GET['step'] == 3) { - echo cardStart($title = "Database Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasedriver">Database Driver</label> - <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required - value="mysql" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasehost">Database Host</label> - <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required - value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseport">Database Port</label> - <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required - value="3306" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required - value="ctrlpaneluser" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuserpass">Database User Password</label> - <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" - required - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col"> - <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkDB">Submit - </button> - </div> - </form> - - <?php -} - -// DB Migration & APP_KEY Generation -if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> - - <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="feedDB"> - - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="feedDB">Submit - </button> - </div> - </form> - <?php - } - - // Redis Config - if (isset($_GET['step']) && $_GET['step'] == 4) { - echo cardStart($title = "Redis Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="redisSetup"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="redishost">Redis Host</label> - <input x-model="redishost" id="redishost" name="redishost" type="text" required - value="<?php echo(determineIfRunningInDocker() ? 'redis' : '127.0.0.1') ?>" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="redisport">Redis Port</label> - <input x-model="redisport" id="redisport" name="redisport" type="number" required - value="6379" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="redispassword">Redis Password (optionally, only if configured)</label> - <input x-model="redispassword" id="redispassword" name="redispassword" type="text" - placeholder="usually can be left blank" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="redisSetup">Submit - </button> - </div> - </form> - - <?php - } - - // Dashboard Config - if (isset($_GET['step']) && $_GET['step'] == 5) { - echo cardStart($title = "Dashboard Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkGeneral"> - - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="database">Dashboard URL</label> - <input id="url" name="url" type="text" required - value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="name">Dashboard Name</label> - <input id="name" name="name" type="text" required value="CtrlPanel" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkGeneral">Submit - </button> - </div> - </form> - - - <?php - } - - // Email Config - if (isset($_GET['step']) && $_GET['step'] == 6) { - echo cardStart($title = "E-Mail Configuration", $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkSMTP"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="method">E-Mail Protocol</label> - <select id="method" name="method" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="smtp" selected>SMTP</option> - </select> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="host">Your Mailer-Host</label> - <input id="host" name="host" type="text" required value="smtp.google.com" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="port">Your Mail Port</label> - <input id="port" name="port" type="number" required value="567" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="user">Your Mail User</label> - <input id="user" name="user" type="text" required value="info@mydomain.com" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Your Mail-User Password</label> - <input id="pass" name="pass" type="password" required value="" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col"> - <label for="encryption">Your Mail encryption method</label> - <select id="encryption" name="encryption" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="tls" selected>TLS</option> - <option value="ssl">SSL</option> - <option value="null">None</option> - </select> - </div> - </div> - </div> - </div> - - - <div class="flex w-full justify-around mt-4 gap-8 px-8"> - <button type="submit" - class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkSMTP">Submit - </button> - - <a href="?step=7" class="w-full"> - <button type="button" - class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600"> - Skip For Now - </button> - </a> - </div> - </form> - - - <?php - } - - // Pterodactyl Config - if (isset($_GET['step']) && $_GET['step'] == 7) { - echo cardStart($title = "Pterodactyl Configuration", $subtitle = "Lets get some info about your Pterodactyl Installation!"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkPtero"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - - <label for="url">Pterodactyl URL</label> - <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="key">Application API Key</label> - <input id="key" name="key" type="text" required value="" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/admin/api] <br/> The key needs all - Read & Write permissions! </span> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="clientkey">Admin User Client API Key</label> - <input id="clientkey" name="clientkey" type="text" required value="" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/account/api] <br/> Your Account - needs to be an Admin!</span> - </div> - </div> - </div> - </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkPtero">Submit - </button> - </div> - </form> - - - <?php - } - - // Admin Creation Form - if (isset($_GET['step']) && $_GET['step'] == 8) { - echo cardStart($title = "First Admin Creation", $subtitle = "Lets create the first admin user!"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="createUser"> - - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pteroID">Pterodactyl User ID </label> - <input id="pteroID" name="pteroID" type="text" required value="1" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Password</label> - <input id="pass" name="pass" type="password" required value="" minlength="8" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="repass">Confirm Password</label> - <input id="repass" name="repass" type="password" required value="" minlength="8" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="createUser">Submit - </button> - </div> - </form> - - - <?php - } - - // Install Finished - if (isset($_GET['step']) && $_GET['step'] == 9) { - $lockfile = fopen('../../installer.lock', 'w') or exit('Unable to open file!'); - fwrite($lockfile, 'locked'); - fclose($lockfile); - - echo cardStart($title = "Installation Complete!", $subtitle = "You may navigate to your Dashboard now and log in!"); - ?> - - <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> - <button - class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500"> - Lets Go! - </button> - </a> - - <?php - } - ?> -</body> -</html> +$viewNames = [ + 1 => 'mandatory-checks', + 2 => 'timezone-configuration', + 3 => 'database-configuration', + 4 => 'database-migration', + 5 => 'redis-configuration', + 6 => 'dashboard-configuration', + 7 => 'email-configuration', + 8 => 'pterodactyl-configuration', + 9 => 'admin-creation', + 10 => 'installation-complete', +]; + +$step = isset($_GET['step']) ? $_GET['step'] : 1; +$viewName = $viewNames[$step]; // Get the appropriate view name + +// Load the layout and the specific view file +include './views/layout-top.php'; +include "./views/{$viewName}.php"; +include './views/layout-bottom.php'; + +?> \ No newline at end of file diff --git a/public/installer/views/admin-creation.php b/public/installer/views/admin-creation.php index e69de29bb..b2eb9fa4f 100644 --- a/public/installer/views/admin-creation.php +++ b/public/installer/views/admin-creation.php @@ -0,0 +1,48 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "First Admin Creation", + $subtitle = "Lets create the first admin user!" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="createUser"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pteroID">Pterodactyl User ID </label> + <input id="pteroID" name="pteroID" type="text" required value="1" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Password</label> + <input id="pass" name="pass" type="password" required value="" minlength="8" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="repass">Confirm Password</label> + <input id="repass" name="repass" type="password" required value="" minlength="8" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="createUser">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php index e69de29bb..642b0ddf5 100644 --- a/public/installer/views/dashboard-configuration.php +++ b/public/installer/views/dashboard-configuration.php @@ -0,0 +1,42 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Dashboard Configuration" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkGeneral"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="database">Dashboard URL</label> + <input id="url" name="url" type="text" required + value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="name">Dashboard Name</label> + <input id="name" name="name" type="text" required value="CtrlPanel" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkGeneral">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/database-configuration.php b/public/installer/views/database-configuration.php index e69de29bb..5aa96dd20 100644 --- a/public/installer/views/database-configuration.php +++ b/public/installer/views/database-configuration.php @@ -0,0 +1,75 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Database Configuration" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkDB"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasedriver">Database Driver</label> + <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required + value="mysql" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasehost">Database Host</label> + <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required + value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseport">Database Port</label> + <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required + value="3306" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuser">Database User</label> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required + value="ctrlpaneluser" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuserpass">Database User Password</label> + <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" + required + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col"> + <label for="database">Database</label> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkDB">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index e69de29bb..cf12ba40c 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -0,0 +1,23 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Database Migration and Encryption Key Generation", + $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="feedDB"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="feedDB">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/email-configuration.php b/public/installer/views/email-configuration.php index e69de29bb..a7545fdae 100644 --- a/public/installer/views/email-configuration.php +++ b/public/installer/views/email-configuration.php @@ -0,0 +1,80 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "E-Mail Configuration", + $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkSMTP"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="method">E-Mail Protocol</label> + <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="smtp" selected>SMTP</option> + </select> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="host">Your Mailer-Host</label> + <input id="host" name="host" type="text" required value="smtp.google.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="port">Your Mail Port</label> + <input id="port" name="port" type="number" required value="567" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="user">Your Mail User</label> + <input id="user" name="user" type="text" required value="info@mydomain.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Your Mail-User Password</label> + <input id="pass" name="pass" type="password" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col"> + <label for="encryption">Your Mail encryption method</label> + <select id="encryption" name="encryption" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="tls" selected>TLS</option> + <option value="ssl">SSL</option> + <option value="null">None</option> + </select> + </div> + </div> + </div> + </div> + + <div class="flex w-full justify-around mt-4 gap-8 px-8"> + <button type="submit" + class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkSMTP">Submit + </button> + + <a href="?step=7" class="w-full"> + <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600"> + Skip For Now + </button> + </a> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/installation-complete.php b/public/installer/views/installation-complete.php index e69de29bb..c9e04d23d 100644 --- a/public/installer/views/installation-complete.php +++ b/public/installer/views/installation-complete.php @@ -0,0 +1,22 @@ + +<!-- top layout here --> + +<?php +$lockfile = fopen('../../installer.lock', 'w') or exit('Unable to open file!'); +fwrite($lockfile, 'the installation is locked, delete this file to unlock it'); +fclose($lockfile); + +echo cardStart( + $title = "Installation Complete!", + $subtitle = "You may navigate to your Dashboard now and log in!" +); +?> + +<a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> + <button + class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500"> + Lets Go! + </button> +</a> + +<!-- bottom layout here --> diff --git a/public/installer/views/layout-bottom.php b/public/installer/views/layout-bottom.php new file mode 100644 index 000000000..35bfcba04 --- /dev/null +++ b/public/installer/views/layout-bottom.php @@ -0,0 +1,7 @@ + +<!-- top layout here --> + +<!-- any middle view here --> + + </body> +</html> diff --git a/public/installer/views/layout-top.php b/public/installer/views/layout-top.php new file mode 100644 index 000000000..3d2b89382 --- /dev/null +++ b/public/installer/views/layout-top.php @@ -0,0 +1,59 @@ +<html lang="en"> +<head> + <title>CtrlPanel.gg installer Script</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link href="/installer/styles.css" rel="stylesheet"> + <style> + body { + color-scheme: dark; + } + + .check { + display: flex; + gap: 5px; + align-items: center; + margin-bottom: 5px; + } + + .check::before { + width: 20px; + height: 20px; + display: block; + } + + .ok { + color: lightgreen; + } + + /* Green Checkmark */ + .ok::before { + content: url("data:image/svg+xml,%3Csvg fill='none' stroke='lightgreen' stroke-width='1.5' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); + } + + .not-ok { + color: lightcoral; + } + + /* Red Cross */ + .not-ok::before { + content: url("data:image/svg+xml,%3Csvg fill='none' stroke='lightcoral' stroke-width='1.5' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); + } + </style> +</head> + +<body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> + <?php + function cardStart($title, $subtitle = null): string + { + return " + <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> + <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> + <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> + <h2 class='text-xl text-center mb-2'>$title</h2>" + . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); + } + ?> + +<!-- any middle view here --> + +<!-- bottom layout here --> diff --git a/public/installer/views/mandatory-checks.php b/public/installer/views/mandatory-checks.php index e69de29bb..039f378c4 100644 --- a/public/installer/views/mandatory-checks.php +++ b/public/installer/views/mandatory-checks.php @@ -0,0 +1,54 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Mandatory Checks before Installation", + $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup" +); ?> + +<ul class="list-none mb-2"> + + <li class="<?php echo checkWriteable() ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> + + <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) + </li> + + <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> + Missing php-extentions: + <?php echo count(checkExtensions()) == 0 ? 'none' : ''; + foreach (checkExtensions() as $ext) { + echo $ext . ', '; + } + echo count(checkExtensions()) === 0 ? '' : '(Proceed anyway)'; ?> + </li> + + <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Git version: + <?php echo getGitVersion(); ?> + </li> + + <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Tar version: + <?php echo getTarVersion(); ?> + </li> + + <li> + <p class="text-neutral-400 mb-1"> + <br> + <span style="color: #eab308;">Important:</span> + CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> + Please make sure you have these installed and running before you continue. + </p> + </li> + +</ul> + +<a href="?step=2" class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> + Lets go! + </button> +</a> + +<!-- bottom layout here --> diff --git a/public/installer/views/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php index e69de29bb..47ca4acda 100644 --- a/public/installer/views/pterodactyl-configuration.php +++ b/public/installer/views/pterodactyl-configuration.php @@ -0,0 +1,52 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Pterodactyl Configuration", + $subtitle = "Lets get some info about your Pterodactyl Installation!" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkPtero"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="url">Pterodactyl URL</label> + <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="key">Application API Key</label> + <input id="key" name="key" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400"> + [Found at: ptero.example.com/admin/api] <br/> + The key needs all Read & Write permissions! </span> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="clientkey">Admin User Client API Key</label> + <input id="clientkey" name="clientkey" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400"> + [Found at: ptero.example.com/account/api] <br/> + Your Account needs to be an Admin!</span> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkPtero">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index e69de29bb..76bc67380 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -0,0 +1,51 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Redis Configuration" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="redisSetup"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="redishost">Redis Host</label> + <input x-model="redishost" id="redishost" name="redishost" type="text" required + value="<?php echo(determineIfRunningInDocker() ? 'redis' : '127.0.0.1') ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="redisport">Redis Port</label> + <input x-model="redisport" id="redisport" name="redisport" type="number" required + value="6379" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="redispassword">Redis Password (optionally, only if configured)</label> + <input x-model="redispassword" id="redispassword" name="redispassword" type="text" + placeholder="usually can be left blank" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="redisSetup">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index e69de29bb..0d2228773 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -0,0 +1,42 @@ +<!-- top layout here --> + +<?php echo cardStart( + $title = "Timezone Configuration" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="timezoneConfig"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="timezone">Timezone</label> + <select id="timezone" name="timezone" required + class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <?php + foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { + if ($timezoneIdentifier === 'UTC') { + continue; + } + + echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; + } ?> + </select> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="timezoneConfig">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> \ No newline at end of file From b9a1cd8e52054ac5286b44dcfc8ab8a272083e99 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 11:17:55 +0200 Subject: [PATCH 409/514] fix: changed database oops reverting back change --- config/database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/database.php b/config/database.php index 1917bc6a7..dc722b5fd 100644 --- a/config/database.php +++ b/config/database.php @@ -119,7 +119,7 @@ 'redis' => [ - 'client' => env('REDIS_CLIENT', 'predis'), + 'client' => env('REDIS_CLIENT', 'phpredis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), From b45566c7218fcdd1b66c9641dee1c284caca7c68 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 15:14:28 +0200 Subject: [PATCH 410/514] refactor: too many things, refactored the form and functions and added better step and error handling --- public/installer/forms.php | 372 ------------------ public/installer/functions.php | 280 ------------- public/installer/index.php | 51 ++- public/installer/src/forms/admin.php | 96 +++++ public/installer/src/forms/dashboard.php | 29 ++ public/installer/src/forms/database.php | 62 +++ public/installer/src/forms/pterodactyl.php | 80 ++++ public/installer/src/forms/redis.php | 32 ++ public/installer/src/forms/smtp.php | 71 ++++ public/installer/src/forms/timezone.php | 13 + public/installer/src/functions/database.php | 0 .../installer/src/functions/environment.php | 132 +++++++ public/installer/src/functions/installer.php | 16 + public/installer/src/functions/logging.php | 45 +++ public/installer/src/functions/shell.php | 41 ++ public/installer/src/functions/utils.php | 41 ++ public/installer/views/admin-creation.php | 6 +- .../views/dashboard-configuration.php | 6 +- .../views/database-configuration.php | 6 +- public/installer/views/database-migration.php | 6 +- .../installer/views/email-configuration.php | 6 +- .../views/pterodactyl-configuration.php | 6 +- .../installer/views/redis-configuration.php | 6 +- .../views/timezone-configuration.php | 6 +- 24 files changed, 721 insertions(+), 688 deletions(-) delete mode 100644 public/installer/forms.php delete mode 100644 public/installer/functions.php delete mode 100644 public/installer/src/functions/database.php create mode 100644 public/installer/src/functions/installer.php diff --git a/public/installer/forms.php b/public/installer/forms.php deleted file mode 100644 index af5fba754..000000000 --- a/public/installer/forms.php +++ /dev/null @@ -1,372 +0,0 @@ -<?php -ini_set('display_errors', 1); -ini_set('display_startup_errors', 1); -error_reporting(E_ALL); - -use PHPMailer\PHPMailer\Exception; -use PHPMailer\PHPMailer\PHPMailer; -use Predis\Client; - -require './src/phpmailer/Exception.php'; -require './src/phpmailer/PHPMailer.php'; -require './src/phpmailer/SMTP.php'; - -if (isset($_POST['timezoneConfig'])) { - wh_log('Setting up Timezone', 'debug'); - $timezone = $_POST['timezone']; - - setenv('APP_TIMEZONE', $timezone); - - wh_log('Timezone set: ' . $timezone, 'debug'); - header('LOCATION: index.php?step=3'); -} - -mysqli_report(MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ALL); - -if (isset($_POST['checkDB'])) { - $values = [ - //SETTINGS::VALUE => REQUEST-VALUE (coming from the html-form) - 'DB_HOST' => 'databasehost', - 'DB_DATABASE' => 'database', - 'DB_USERNAME' => 'databaseuser', - 'DB_PASSWORD' => 'databaseuserpass', - 'DB_PORT' => 'databaseport', - 'DB_CONNECTION' => 'databasedriver', - ]; - - wh_log('Trying to connect to the Database', 'debug'); - - try { - $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); - } catch (mysqli_sql_exception $e) { - wh_log($e->getMessage(), 'error'); - header('LOCATION: index.php?step=3&message=' . $e->getMessage()); - exit(); - } - - - foreach ($values as $key => $value) { - $param = $_POST[$value]; - // if ($key == "DB_PASSWORD") { - // $param = '"' . $_POST[$value] . '"'; - // } - setenv($key, $param); - } - - wh_log('Database connection successful', 'debug'); - header('LOCATION: index.php?step=3.5'); -} - -if (isset($_POST['feedDB'])) { - wh_log('Feeding the Database', 'debug'); - $logs = ''; - - try { - //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); - //$logs .= run_console('composer install --no-dev --optimize-autoloader'); - if (!str_contains(getenv('APP_KEY'), 'base64')) { - $logs .= run_console('php artisan key:generate --force'); - } else { - $logs .= "Key already exists. Skipping\n"; - } - $logs .= run_console('php artisan storage:link'); - $logs .= run_console('php artisan migrate --seed --force'); - $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); - $logs .= run_console('php artisan db:seed --class=PermissionsSeeder --force'); - - wh_log($logs, 'debug'); - - wh_log('Feeding the Database successful', 'debug'); - header('LOCATION: index.php?step=4'); - } catch (Throwable $th) { - wh_log('Feeding the Database failed', 'error'); - header("LOCATION: index.php?step=3.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); - } -} - -if (isset($_POST['redisSetup'])) { - wh_log('Setting up Redis', 'debug'); - $redisHost = $_POST['redishost']; - $redisPort = $_POST['redisport']; - $redisPassword = $_POST['redispassword']; - - $redisClient = new Client([ - 'host' => $redisHost, - 'port' => $redisPort, - 'password' => $redisPassword, - 'timeout' => 1.0, - ]); - - try { - $redisClient->ping(); - - setenv('MEMCACHED_HOST', $redisHost); - setenv('REDIS_HOST', $redisHost); - setenv('REDIS_PORT', $redisPort); - setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); - - wh_log('Redis connection successful. Settings updated.', 'debug'); - header('LOCATION: index.php?step=5'); - } catch (Throwable $th) { - wh_log('Redis connection failed. Settings updated.', 'debug'); - header("LOCATION: index.php?step=4&message=Please check your credentials!<br>" . $th->getMessage()); - } -} - -if (isset($_POST['checkGeneral'])) { - wh_log('setting app settings', 'debug'); - $appname = '"' . $_POST['name'] . '"'; - $appurl = $_POST['url']; - - $parsedUrl = parse_url($appurl); - - if (!isset($parsedUrl['scheme'])) { - header('LOCATION: index.php?step=5&message=Please set an URL Scheme like "https://"!'); - exit(); - } - - if (!isset($parsedUrl['host'])) { - header('LOCATION: index.php?step=5&message=Please set an valid URL host like "https://ctrlpanel.example.com"!'); - exit(); - } - - $appurl = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; - - setenv('APP_NAME', $appname); - setenv('APP_URL', $appurl); - - wh_log('App settings set', 'debug'); - header('LOCATION: index.php?step=6'); -} - -if (isset($_POST['checkSMTP'])) { - wh_log('Checking SMTP Settings', 'debug'); - try { - $mail = new PHPMailer(true); - - //Server settings - // Send using SMTP - $mail->isSMTP(); - $mail->Host = $_POST['host']; - // Enable SMTP authentication - $mail->SMTPAuth = true; - $mail->Username = $_POST['user']; - $mail->Password = $_POST['pass']; - $mail->SMTPSecure = $_POST['encryption']; - $mail->Port = (int) $_POST['port']; - - // Test E-mail metadata - $mail->setFrom($_POST['user'], $_POST['user']); - $mail->addAddress($_POST['user'], $_POST['user']); - - // Content - // Set email format to HTML - $mail->isHTML(true); - $mail->Subject = 'It Worked! - Test E-Mail from Ctrlpanel.gg'; - $mail->Body = 'Your E-Mail Settings are correct!'; - - $mail->send(); - } catch (Exception $e) { - wh_log($mail->ErrorInfo, 'error'); - header('LOCATION: index.php?step=6&message=Something went wrong while sending test E-Mail!<br>' . $mail->ErrorInfo); - exit(); - } - - wh_log('SMTP Settings are correct', 'debug'); - wh_log('Updating Database', 'debug'); - $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); - if ($db->connect_error) { - wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=6&message=Could not connect to the Database: '); - exit(); - } - $values = [ - 'mail_mailer' => $_POST['method'], - 'mail_host' => $_POST['host'], - 'mail_port' => $_POST['port'], - 'mail_username' => $_POST['user'], - 'mail_password' => $_POST['pass'], - 'mail_encryption' => $_POST['encryption'], - 'mail_from_address' => $_POST['user'], - ]; - - foreach ($values as $key => $value) { - run_console("php artisan settings:set 'MailSettings' '$key' '$value'"); - } - - wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=7'); -} - -if (isset($_POST['checkPtero'])) { - wh_log('Checking Pterodactyl Settings', 'debug'); - - $url = $_POST['url']; - $key = $_POST['key']; - $clientkey = $_POST['clientkey']; - - $parsedUrl = parse_url($url); - - if (!isset($parsedUrl['scheme'])) { - header('LOCATION: index.php?step=7&message=Please set an URL Scheme like "https://"!'); - exit(); - } - - if (!isset($parsedUrl['host'])) { - header('LOCATION: index.php?step=7&message=Please set an valid URL host like "https://panel.example.com"!'); - exit(); - } - - $url = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; - - $callpteroURL = $url . '/api/client/account'; - $call = curl_init(); - - curl_setopt($call, CURLOPT_URL, $callpteroURL); - curl_setopt($call, CURLOPT_RETURNTRANSFER, true); - curl_setopt($call, CURLOPT_HTTPHEADER, [ - 'Accept: Application/vnd.pterodactyl.v1+json', - 'Content-Type: application/json', - 'Authorization: Bearer ' . $clientkey, - ]); - $callresponse = curl_exec($call); - $callresult = json_decode($callresponse, true); - curl_close($call); - - $pteroURL = $url . '/api/application/users'; - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $pteroURL); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Accept: Application/vnd.pterodactyl.v1+json', - 'Content-Type: application/json', - 'Authorization: Bearer ' . $key, - ]); - $response = curl_exec($ch); - $result = json_decode($response, true); - curl_close($ch); - - if (!is_array($result)) { - wh_log('No array in response found', 'error'); - header('LOCATION: index.php?step=7&message=An unknown Error occured, please try again!'); - } - - if (array_key_exists('errors', $result) && $result['errors'][0]['detail'] === 'This action is unauthorized.') { - wh_log('API CALL ERROR: ' . $result['errors'][0]['code'], 'error'); - header('LOCATION: index.php?step=7&message=Couldn\'t connect to Pterodactyl. Make sure your Application API key has all read and write permissions!'); - exit(); - } - - if (array_key_exists('errors', $callresult) && $callresult['errors'][0]['detail'] === 'Unauthenticated.') { - wh_log('API CALL ERROR: ' . $callresult['errors'][0]['code'], 'error'); - header('LOCATION: index.php?step=7&message=Your ClientAPI Key is wrong or the account is not an admin!'); - exit(); - } - - try { - run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'"); - run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); - run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); - wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=8'); - } catch (Throwable $th) { - wh_log("Setting Pterodactyl information failed.", 'error'); - header("LOCATION: index.php?step=7&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); - exit(); - } -} - -if (isset($_POST['createUser'])) { - wh_log('Getting Pterodactyl User', 'debug'); - - try { - $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); - } catch (Throwable $th) { - wh_log($th->getMessage(), 'error'); - header('LOCATION: index.php?step=8&message=Could not connect to the Database'); - exit(); - } - - $pteroID = $_POST['pteroID']; - $pass = $_POST['pass']; - $repass = $_POST['repass']; - - try { - $panelUrl = run_console("php artisan settings:get 'PterodactylSettings' 'panel_url' --sameline"); - $adminToken = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); - } catch (Throwable $th) { - wh_log("Getting Pterodactyl information failed.", 'error'); - header("LOCATION: index.php?step=7&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); - exit(); - } - - $panelApiUrl = $panelUrl . '/api/application/users/' . $pteroID; - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $panelApiUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Accept: application/json', - 'Content-Type: application/json', - 'Authorization: Bearer ' . $adminToken, - ]); - $response = curl_exec($ch); - $result = json_decode($response, true); - curl_close($ch); - - if ($pass !== $repass) { - header('LOCATION: index.php?step=8&message=The Passwords did not match!'); - exit(); - } - - if (array_key_exists('errors', $result)) { - header('LOCATION: index.php?step=8&message=Could not find the user with pterodactyl ID ' . $pteroID); - exit(); - } - - $mail = $result['attributes']['email']; - $name = $result['attributes']['username']; - $pass = password_hash($pass, PASSWORD_DEFAULT); - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $panelApiUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Accept: application/json', - 'Content-Type: application/json', - 'Authorization: Bearer ' . $adminToken, - ]); - curl_setopt($ch, CURLOPT_POSTFIELDS, [ - 'email' => $mail, - 'username' => $name, - 'first_name' => $name, - 'last_name' => $name, - 'password' => $pass, - ]); - $response = curl_exec($ch); - $result = json_decode($response, true); - curl_close($ch); - - $random = generateRandomString(); - - $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; - $query2 = "INSERT INTO `" . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\\\Models\\\User', '1')"; - try { - $db->query($query1); - $db->query($query2); - - wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID); - header('LOCATION: index.php?step=9'); - } catch (Throwable $th) { - wh_log($th->getMessage(), 'error'); - if (str_contains($th->getMessage(), 'Duplicate entry')) { - header('LOCATION: index.php?step=8&message=User already exists in CtrlPanel\'s Database.'); - } else { - header('LOCATION: index.php?step=8&message=Something went wrong when communicating with the Database.'); - } - exit(); - } -} diff --git a/public/installer/functions.php b/public/installer/functions.php deleted file mode 100644 index e450e8cc1..000000000 --- a/public/installer/functions.php +++ /dev/null @@ -1,280 +0,0 @@ -<?php -require '../../vendor/autoload.php'; -require 'dotenv.php'; - -use DevCoder\DotEnv; -use Illuminate\Encryption\Encrypter; -use Illuminate\Support\Str; -use Monolog\Formatter\LineFormatter; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; - -if (!file_exists('../../.env')) { - echo run_console('cp .env.example .env'); -} - -(new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); - -$required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl', 'redis']; - -$requirements = [ - 'minPhp' => '8.1', - 'maxPhp' => '8.4', // This version is not supported - 'mysql' => '5.7.22', -]; - -/** - * Check if the minimum PHP version is present - * @return string 'OK' on success and 'not OK' on failure. - */ -function checkPhpVersion(): string -{ - global $requirements; - - wh_log('php version: ' . phpversion(), 'debug'); - if (version_compare(phpversion(), $requirements['minPhp'], '>=') && version_compare(phpversion(), $requirements['maxPhp'], '<=')) { - return 'OK'; - } - - return 'not OK'; -} - -/** - * Check if the environment file is writable - * @return bool Returns true on writable and false on not writable. - */ -function checkWriteable(): bool -{ - return is_writable('../../.env'); -} - -/** - * Check if zip is installed using a shell command - * @return string 'OK' on success and 'not OK' on failure. - */ -function getZipVersion(): string -{ - wh_log('attempting to get zip version', 'debug'); - $output = shell_exec('zip -v') ?? ''; - preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); - - $versionoutput = $version[0] ?? 0; - wh_log('zip version: ' . $versionoutput, 'debug'); - - return $versionoutput != 0 ? 'OK' : 'not OK'; -} - -/** - * Check if git is installed using a shell command - * @return string 'OK' on success and 'not OK' on failure. - */ -function getGitVersion(): string -{ - wh_log('attempting to get git version', 'debug'); - $output = shell_exec('git --version') ?? ''; - preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); - - $versionoutput = $version[0] ?? 0; - wh_log('git version: ' . $versionoutput, 'debug'); - - return $versionoutput != 0 ? 'OK' : 'not OK'; -} - -/** - * Check if tar is installed using a shell command - * @return string 'OK' on success and 'not OK' on failure. - */ -function getTarVersion(): string -{ - wh_log('attempting to get tar version', 'debug'); - $output = shell_exec('tar --version') ?? ''; - preg_match('@[0-9]+\.[0-9]+@', $output, $version); - - $versionoutput = $version[0] ?? 0; - wh_log('tar version: ' . $versionoutput, 'debug'); - - return $versionoutput != 0 ? 'OK' : 'not OK'; -} - -/** - * Check all extensions to see if they have loaded or not - * @return array Returns an array of extensions that failed to load. - */ -function checkExtensions(): array -{ - global $required_extensions; - - wh_log('checking extensions', 'debug'); - - $not_ok = []; - $extentions = get_loaded_extensions(); - - foreach ($required_extensions as $ext) { - if (!preg_grep('/^(?=.*' . $ext . ').*$/', $extentions)) { - array_push($not_ok, $ext); - } - } - - wh_log('loaded extensions:', 'debug', $extentions); - wh_log('failed extensions:', 'debug', $not_ok); - return $not_ok; -} - -function removeQuotes($string) -{ - return str_replace('"', "", $string); -} - -/** - * Sets the environment variable into the env file - * @param string $envKey The environment key to set or modify - * @param string $envValue The environment variable to set - * @return bool true on success or false on failure. - */ -function setenv($envKey, $envValue) -{ - $envFile = dirname(__FILE__, 3) . '/.env'; - $str = file_get_contents($envFile); - - $str .= "\n"; // In case the searched variable is in the last line without \n - $keyPosition = strpos($str, "{$envKey}="); - $endOfLinePosition = strpos($str, PHP_EOL, $keyPosition); - $oldLine = substr($str, $keyPosition, $endOfLinePosition - $keyPosition); - $str = str_replace($oldLine, "{$envKey}={$envValue}", $str); - $str = substr($str, 0, -1); - - $fp = fopen($envFile, 'w'); - fwrite($fp, $str); - fclose($fp); -} - -/** - * Encrypt the given value - * @param mixed $value The variable to be encrypted - * @param bool $serialize If the encryption should be serialized - * @return string Returns the encrypted variable. - */ -function encryptSettingsValue(mixed $value, $serialize = true): string -{ - $appKey = getenv('APP_KEY'); - $appKey = base64_decode(Str::after($appKey, 'base64:')); - $encrypter = new Encrypter($appKey, 'AES-256-CBC'); - $encryptedKey = $encrypter->encrypt($value, $serialize); - - return $encryptedKey; -} - -/** - * Decrypt the given value - * @param mixed $payload The payload to be decrypted - * @param bool $unserialize If the encryption should be unserialized - * @return mixed Returns the decrypted variable on success, throws otherwise. - */ - -function decryptSettingsValue(mixed $payload, $unserialize = true) -{ - $appKey = getenv('APP_KEY'); - $appKey = base64_decode(Str::after($appKey, 'base64:')); - $encrypter = new Encrypter($appKey, 'AES-256-CBC'); - $decryptedKey = $encrypter->decrypt($payload, $unserialize); - - return $decryptedKey; -} - -/** - * Run a shell command - * @param string $command The command string to run - * @param array|null $descriptors [optional]<p> - * An indexed array where the key represents the descriptor number and the value represents how PHP will pass that descriptor to the child process. 0 is stdin, 1 is stdout, while 2 is stderr. - * Default descriptors when null are 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] - * </p> - * @param string|null $cwd [optional] <p> - * The initial working dir for the command. This must be an - * absolute directory path, or null - * if you want to use the default value (the working dir of the current - * PHP process) - * </p> - * @param array|null $options [optional] <p> - * Allows you to specify additional options. - * @link https://www.php.net/manual/en/function.proc-open.php proc_open - * </p> - * @return false|string|null Returns the result from the command. - */ -function run_console(string $command, array $descriptors = null, string $cwd = null, array $options = null) -{ - wh_log('running command: ' . $command, 'debug'); - - $path = dirname(__FILE__, 3); - $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); - $output = stream_get_contents($pipes[1]); - $exit_code = proc_close($handle); - - if ($exit_code > 0) { - wh_log('command result: ' . $output, 'error'); - throw new Exception("There was an error after running command `$command`", $exit_code); - return $output; - } else { - return $output; - } -} - -/** - * Log to the default laravel.log file - * @param string $message The message to log - * @param string $level The log level to use (debug, info, warning, error, critical) - * @param array $context [optional] The context to log extra information - * @return void - */ -function wh_log(string $message, string $level = 'info', array $context = []): void -{ - $formatter = new LineFormatter(null, null, true, true); - $stream = new StreamHandler(dirname(__FILE__, 3) . '/storage/logs/installer.log', Logger::DEBUG); - $stream->setFormatter($formatter); - - $log = new Logger('CtrlPanel'); - $log->pushHandler($stream); - - switch (strtolower($level)) { - case 'debug': // Only log debug messages if APP_DEBUG is true - if (getenv('APP_DEBUG') === false) return; - $log->debug($message, $context); - break; - case 'info': - $log->info($message, $context); - break; - case 'warning': - $log->warning($message, $context); - break; - case 'error': - $log->error($message, $context); - break; - case 'critical': - $log->critical($message, $context); - break; - } - // Prevent memory leaks by resetting the logger - $log->reset(); -} - -/** - * Generate a random string - * @param int $length The length of the random string - * @return string The randomly generated string. - */ -function generateRandomString(int $length = 8): string -{ - $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $charactersLength = strlen($characters); - $randomString = ''; - for ($i = 0; $i < $length; $i++) { - $randomString .= $characters[rand(0, $charactersLength - 1)]; - } - - return $randomString; -} - -function determineIfRunningInDocker(): bool -{ - return file_exists('/.dockerenv'); -} diff --git a/public/installer/index.php b/public/installer/index.php index 24ee47bbd..4feed33b6 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -1,28 +1,43 @@ <?php +ini_set('display_errors', 1); +ini_set('display_startup_errors', 1); +error_reporting(E_ALL); + +session_start(); + +use DevCoder\DotEnv; +// use Illuminate\Encryption\Encrypter; +// use Illuminate\Support\Str; + +require '../../vendor/autoload.php'; +require 'dotenv.php'; // Include the function files +require_once './src/functions/installer.php'; // very important require_once './src/functions/environment.php'; -require_once './src/functions/database.php'; require_once './src/functions/shell.php'; require_once './src/functions/logging.php'; require_once './src/functions/utils.php'; // Include the form files -require_once './src/forms/timezone.php'; -require_once './src/forms/database.php'; -require_once './src/forms/redis.php'; -require_once './src/forms/dashboard.php'; -require_once './src/forms/smtp.php'; -require_once './src/forms/pterodactyl.php'; -require_once './src/forms/admin.php'; - -require_once './functions.php'; -require_once './forms.php'; +include './src/forms/timezone.php'; +include './src/forms/database.php'; +include './src/forms/redis.php'; +include './src/forms/dashboard.php'; +include './src/forms/smtp.php'; +include './src/forms/pterodactyl.php'; +include './src/forms/admin.php'; if (file_exists('../../install.lock')) { exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } +if (!file_exists('../../.env')) { + echo run_console('cp .env.example .env'); +} + +(new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); + $viewNames = [ 1 => 'mandatory-checks', 2 => 'timezone-configuration', @@ -36,7 +51,16 @@ 10 => 'installation-complete', ]; -$step = isset($_GET['step']) ? $_GET['step'] : 1; +// Prioritize $_GET['step'], then session, then default to 1 +$step = isset($_GET['step']) + ? (int)$_GET['step'] // Convert to integer for safety + : (isset($_SESSION['installation_step']) + ? $_SESSION['installation_step'] + : 1); + +// Update session with the current step +$_SESSION['installation_step'] = $step; + $viewName = $viewNames[$step]; // Get the appropriate view name // Load the layout and the specific view file @@ -44,4 +68,7 @@ include "./views/{$viewName}.php"; include './views/layout-bottom.php'; +// setting / reseting the error message +$_SESSION['error-message'] = null; + ?> \ No newline at end of file diff --git a/public/installer/src/forms/admin.php b/public/installer/src/forms/admin.php index e69de29bb..9d0df6229 100644 --- a/public/installer/src/forms/admin.php +++ b/public/installer/src/forms/admin.php @@ -0,0 +1,96 @@ +<?php + +if (isset($_POST['createUser'])) { + wh_log('Getting Pterodactyl User', 'debug'); + + try { + $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); + } catch (Throwable $th) { + wh_log($th->getMessage(), 'error'); + send_error_message("Could not connect to the Database"); + exit(); + } + + $pteroID = $_POST['pteroID']; + $pass = $_POST['pass']; + $repass = $_POST['repass']; + + try { + $panelUrl = run_console("php artisan settings:get 'PterodactylSettings' 'panel_url' --sameline"); + $adminToken = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); + } catch (Throwable $th) { + wh_log("Getting Pterodactyl information failed.", 'error'); + send_error_message($th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + exit(); + } + + $panelApiUrl = $panelUrl . '/api/application/users/' . $pteroID; + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $panelApiUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Accept: application/json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $adminToken, + ]); + $response = curl_exec($ch); + $result = json_decode($response, true); + curl_close($ch); + + if ($pass !== $repass) { + send_error_message("The Passwords did not match!"); + exit(); + } + + if (array_key_exists('errors', $result)) { + send_error_message("Could not find the user with pterodactyl ID" . $pteroID); + exit(); + } + + $mail = $result['attributes']['email']; + $name = $result['attributes']['username']; + $pass = password_hash($pass, PASSWORD_DEFAULT); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $panelApiUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Accept: application/json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $adminToken, + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, [ + 'email' => $mail, + 'username' => $name, + 'first_name' => $name, + 'last_name' => $name, + 'password' => $pass, + ]); + $response = curl_exec($ch); + $result = json_decode($response, true); + curl_close($ch); + + $random = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 8); // random referal + + $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; + $query2 = "INSERT INTO `" . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\\\Models\\\User', '1')"; + try { + $db->query($query1); + $db->query($query2); + + wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID); + next_step(); + } catch (Throwable $th) { + wh_log($th->getMessage(), 'error'); + if (str_contains($th->getMessage(), 'Duplicate entry')) { + send_error_message("User already exists in CtrlPanel\'s Database"); + } else { + send_error_message("Something went wrong when communicating with the Database."); + } + exit(); + } +} +?> diff --git a/public/installer/src/forms/dashboard.php b/public/installer/src/forms/dashboard.php index e69de29bb..2229d4133 100644 --- a/public/installer/src/forms/dashboard.php +++ b/public/installer/src/forms/dashboard.php @@ -0,0 +1,29 @@ +<?php + + +if (isset($_POST['checkGeneral'])) { + wh_log('setting app settings', 'debug'); + $appname = '"' . $_POST['name'] . '"'; + $appurl = $_POST['url']; + + $parsedUrl = parse_url($appurl); + + if (!isset($parsedUrl['scheme'])) { + send_error_message("Please set an URL Scheme like 'https://'!"); + exit(); + } + + if (!isset($parsedUrl['host'])) { + send_error_message("Please set an valid URL host like 'https://ctrlpanel.example.com'!"); + exit(); + } + + $appurl = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + + setenv('APP_NAME', $appname); + setenv('APP_URL', $appurl); + + wh_log('App settings set', 'debug'); + next_step(); +} +?> \ No newline at end of file diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index e69de29bb..8e0bdb615 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -0,0 +1,62 @@ +<?php + +mysqli_report(MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ALL); + +if (isset($_POST['checkDB'])) { + $values = [ + //SETTINGS::VALUE => REQUEST-VALUE (coming from the html-form) + 'DB_HOST' => 'databasehost', + 'DB_DATABASE' => 'database', + 'DB_USERNAME' => 'databaseuser', + 'DB_PASSWORD' => 'databaseuserpass', + 'DB_PORT' => 'databaseport', + 'DB_CONNECTION' => 'databasedriver', + ]; + + wh_log('Trying to connect to the Database', 'debug'); + + try { + $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); + } catch (mysqli_sql_exception $e) { + wh_log($e->getMessage(), 'error'); + send_error_message($e->getMessage()); + exit(); + } + + foreach ($values as $key => $value) { + $param = $_POST[$value]; + setenv($key, $param); + } + + wh_log('Database connection successful', 'debug'); + next_step(); +} + +if (isset($_POST['feedDB'])) { + wh_log('Feeding the Database', 'debug'); + $logs = ''; + + try { + //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); + //$logs .= run_console('composer install --no-dev --optimize-autoloader'); + if (!str_contains(getenv('APP_KEY'), 'base64')) { + $logs .= run_console('php artisan key:generate --force'); + } else { + $logs .= "Key already exists. Skipping\n"; + } + $logs .= run_console('php artisan storage:link'); + $logs .= run_console('php artisan migrate --seed --force'); + $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); + $logs .= run_console('php artisan db:seed --class=PermissionsSeeder --force'); + + wh_log($logs, 'debug'); + + wh_log('Feeding the Database successful', 'debug'); + next_step(); + } catch (Throwable $th) { + wh_log('Feeding the Database failed', 'error'); + send_error_message("Feeding the Database failed"); + } +} + +?> diff --git a/public/installer/src/forms/pterodactyl.php b/public/installer/src/forms/pterodactyl.php index e69de29bb..d4c24dd94 100644 --- a/public/installer/src/forms/pterodactyl.php +++ b/public/installer/src/forms/pterodactyl.php @@ -0,0 +1,80 @@ +<?php +if (isset($_POST['checkPtero'])) { + wh_log('Checking Pterodactyl Settings', 'debug'); + + $url = $_POST['url']; + $key = $_POST['key']; + $clientkey = $_POST['clientkey']; + + $parsedUrl = parse_url($url); + + if (!isset($parsedUrl['scheme'])) { + send_error_message("Please set an URL Scheme like 'https://'!"); + exit(); + } + + if (!isset($parsedUrl['host'])) { + send_error_message("Please set an valid URL host like 'https://panel.example.com'!"); + exit(); + } + + $url = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + + $callpteroURL = $url . '/api/client/account'; + $call = curl_init(); + + curl_setopt($call, CURLOPT_URL, $callpteroURL); + curl_setopt($call, CURLOPT_RETURNTRANSFER, true); + curl_setopt($call, CURLOPT_HTTPHEADER, [ + 'Accept: Application/vnd.pterodactyl.v1+json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $clientkey, + ]); + $callresponse = curl_exec($call); + $callresult = json_decode($callresponse, true); + curl_close($call); + + $pteroURL = $url . '/api/application/users'; + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $pteroURL); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Accept: Application/vnd.pterodactyl.v1+json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $key, + ]); + $response = curl_exec($ch); + $result = json_decode($response, true); + curl_close($ch); + + if (!is_array($result)) { + wh_log('No array in response found', 'error'); + send_error_message("An unknown Error occured, please try again!"); + } + + if (array_key_exists('errors', $result) && $result['errors'][0]['detail'] === 'This action is unauthorized.') { + wh_log('API CALL ERROR: ' . $result['errors'][0]['code'], 'error'); + send_error_message("Couldn\'t connect to Pterodactyl. Make sure your Application API key has all read and write permissions!"); + exit(); + } + + if (array_key_exists('errors', $callresult) && $callresult['errors'][0]['detail'] === 'Unauthenticated.') { + wh_log('API CALL ERROR: ' . $callresult['errors'][0]['code'], 'error'); + send_error_message("Your ClientAPI Key is wrong or the account is not an admin!"); + exit(); + } + + try { + run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'"); + run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); + run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); + wh_log('Database updated', 'debug'); + next_step(); + } catch (Throwable $th) { + wh_log("Setting Pterodactyl information failed.", 'error'); + send_error_message($th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + exit(); + } +} +?> \ No newline at end of file diff --git a/public/installer/src/forms/redis.php b/public/installer/src/forms/redis.php index e69de29bb..6837df3ae 100644 --- a/public/installer/src/forms/redis.php +++ b/public/installer/src/forms/redis.php @@ -0,0 +1,32 @@ +<?php + + +if (isset($_POST['redisSetup'])) { + wh_log('Setting up Redis', 'debug'); + $redisHost = $_POST['redishost']; + $redisPort = $_POST['redisport']; + $redisPassword = $_POST['redispassword']; + + $redisClient = new Client([ + 'host' => $redisHost, + 'port' => $redisPort, + 'password' => $redisPassword, + 'timeout' => 1.0, + ]); + + try { + $redisClient->ping(); + + setenv('MEMCACHED_HOST', $redisHost); + setenv('REDIS_HOST', $redisHost); + setenv('REDIS_PORT', $redisPort); + setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); + + wh_log('Redis connection successful. Settings updated.', 'debug'); + next_step(); + } catch (Throwable $th) { + wh_log('Redis connection failed. Settings updated.', 'debug'); + send_error_message("Please check your credentials!<br>" . $th->getMessage()); + } +} +?> diff --git a/public/installer/src/forms/smtp.php b/public/installer/src/forms/smtp.php index e69de29bb..0ec801da9 100644 --- a/public/installer/src/forms/smtp.php +++ b/public/installer/src/forms/smtp.php @@ -0,0 +1,71 @@ +<?php + +use PHPMailer\PHPMailer\Exception; +use PHPMailer\PHPMailer\PHPMailer; +use Predis\Client; + +require './src/phpmailer/Exception.php'; +require './src/phpmailer/PHPMailer.php'; +require './src/phpmailer/SMTP.php'; + + +if (isset($_POST['checkSMTP'])) { + wh_log('Checking SMTP Settings', 'debug'); + try { + $mail = new PHPMailer(true); + + //Server settings + // Send using SMTP + $mail->isSMTP(); + $mail->Host = $_POST['host']; + // Enable SMTP authentication + $mail->SMTPAuth = true; + $mail->Username = $_POST['user']; + $mail->Password = $_POST['pass']; + $mail->SMTPSecure = $_POST['encryption']; + $mail->Port = (int) $_POST['port']; + + // Test E-mail metadata + $mail->setFrom($_POST['user'], $_POST['user']); + $mail->addAddress($_POST['user'], $_POST['user']); + + // Content + // Set email format to HTML + $mail->isHTML(true); + $mail->Subject = 'It Worked! - Test E-Mail from Ctrlpanel.gg'; + $mail->Body = 'Your E-Mail Settings are correct!'; + + $mail->send(); + } catch (Exception $e) { + wh_log($mail->ErrorInfo, 'error'); + send_error_message("Something went wrong while sending test E-Mail!<br>" . $mail->ErrorInfo); + exit(); + } + + wh_log('SMTP Settings are correct', 'debug'); + wh_log('Updating Database', 'debug'); + $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); + if ($db->connect_error) { + wh_log($db->connect_error, 'error'); + send_error_message("Could not connect to the Database"); + exit(); + } + $values = [ + 'mail_mailer' => $_POST['method'], + 'mail_host' => $_POST['host'], + 'mail_port' => $_POST['port'], + 'mail_username' => $_POST['user'], + 'mail_password' => $_POST['pass'], + 'mail_encryption' => $_POST['encryption'], + 'mail_from_address' => $_POST['user'], + ]; + + foreach ($values as $key => $value) { + run_console("php artisan settings:set 'MailSettings' '$key' '$value'"); + } + + wh_log('Database updated', 'debug'); + next_step(); +} + +?> \ No newline at end of file diff --git a/public/installer/src/forms/timezone.php b/public/installer/src/forms/timezone.php index e69de29bb..b3ff5fa2c 100644 --- a/public/installer/src/forms/timezone.php +++ b/public/installer/src/forms/timezone.php @@ -0,0 +1,13 @@ +<?php + +if (isset($_POST['timezoneConfig'])) { + wh_log('Setting up Timezone', 'debug'); + $timezone = $_POST['timezone']; + + setenv('APP_TIMEZONE', $timezone); + + wh_log('Timezone set: ' . $timezone, 'debug'); + next_step(); +} + +?> \ No newline at end of file diff --git a/public/installer/src/functions/database.php b/public/installer/src/functions/database.php deleted file mode 100644 index e69de29bb..000000000 diff --git a/public/installer/src/functions/environment.php b/public/installer/src/functions/environment.php index e69de29bb..3ae262c39 100644 --- a/public/installer/src/functions/environment.php +++ b/public/installer/src/functions/environment.php @@ -0,0 +1,132 @@ +<?php + +/** +* Sets the environment variable into the env file + * @param string $envKey The environment key to set or modify + * @param string $envValue The environment variable to set + * @return bool true on success or false on failure. + */ +function setenv($envKey, $envValue) +{ + $envFile = dirname(__FILE__, 3) . '/.env'; + $str = file_get_contents($envFile); + + $str .= "\n"; // In case the searched variable is in the last line without \n + $keyPosition = strpos($str, "{$envKey}="); + $endOfLinePosition = strpos($str, PHP_EOL, $keyPosition); + $oldLine = substr($str, $keyPosition, $endOfLinePosition - $keyPosition); + $str = str_replace($oldLine, "{$envKey}={$envValue}", $str); + $str = substr($str, 0, -1); + + $fp = fopen($envFile, 'w'); + fwrite($fp, $str); + fclose($fp); +} + + +$required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl', 'redis']; + +/** + * Check all extensions to see if they have loaded or not + * @return array Returns an array of extensions that failed to load. + */ +function checkExtensions(): array +{ + global $required_extensions; + + wh_log('checking extensions', 'debug'); + + $not_ok = []; + $extentions = get_loaded_extensions(); + + foreach ($required_extensions as $ext) { + if (!preg_grep('/^(?=.*' . $ext . ').*$/', $extentions)) { + array_push($not_ok, $ext); + } + } + + wh_log('loaded extensions:', 'debug', $extentions); + wh_log('failed extensions:', 'debug', $not_ok); + return $not_ok; +} + +$requirements = [ + 'minPhp' => '8.1', + 'maxPhp' => '8.4', // This version is not supported + 'mysql' => '5.7.22', +]; + +/** + * Check if the minimum PHP version is present + * @return string 'OK' on success and 'not OK' on failure. + */ +function checkPhpVersion(): string +{ + global $requirements; + + wh_log('php version: ' . phpversion(), 'debug'); + if (version_compare(phpversion(), $requirements['minPhp'], '>=') && version_compare(phpversion(), $requirements['maxPhp'], '<=')) { + return 'OK'; + } + + return 'not OK'; +} + +/** + * Check if zip is installed using a shell command + * @return string 'OK' on success and 'not OK' on failure. + */ +function getZipVersion(): string +{ + wh_log('attempting to get zip version', 'debug'); + $output = shell_exec('zip -v') ?? ''; + preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); + + $versionoutput = $version[0] ?? 0; + wh_log('zip version: ' . $versionoutput, 'debug'); + + return $versionoutput != 0 ? 'OK' : 'not OK'; +} + +/** + * Check if git is installed using a shell command + * @return string 'OK' on success and 'not OK' on failure. + */ +function getGitVersion(): string +{ + wh_log('attempting to get git version', 'debug'); + $output = shell_exec('git --version') ?? ''; + preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); + + $versionoutput = $version[0] ?? 0; + wh_log('git version: ' . $versionoutput, 'debug'); + + return $versionoutput != 0 ? 'OK' : 'not OK'; +} + +/** + * Check if tar is installed using a shell command + * @return string 'OK' on success and 'not OK' on failure. + */ +function getTarVersion(): string +{ + wh_log('attempting to get tar version', 'debug'); + $output = shell_exec('tar --version') ?? ''; + preg_match('@[0-9]+\.[0-9]+@', $output, $version); + + $versionoutput = $version[0] ?? 0; + wh_log('tar version: ' . $versionoutput, 'debug'); + + return $versionoutput != 0 ? 'OK' : 'not OK'; +} + +/** + * Check if the environment file is writable + * @return bool Returns true on writable and false on not writable. + */ +function checkWriteable(): bool +{ + return is_writable('../../.env'); +} + +?> diff --git a/public/installer/src/functions/installer.php b/public/installer/src/functions/installer.php new file mode 100644 index 000000000..24cbf5954 --- /dev/null +++ b/public/installer/src/functions/installer.php @@ -0,0 +1,16 @@ +<?php + +function send_error_message(string $message): void +{ + $_SESSION['error-message'] = $message; + header("LOCATION: index.php"); + exit(); +} + +function next_step(): void +{ + $_SESSION['installation_step']++; + header("LOCATION: index.php"); +} + +?> diff --git a/public/installer/src/functions/logging.php b/public/installer/src/functions/logging.php index e69de29bb..f8151c4ec 100644 --- a/public/installer/src/functions/logging.php +++ b/public/installer/src/functions/logging.php @@ -0,0 +1,45 @@ +<?php + +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; + +/** + * Log to the default laravel.log file + * @param string $message The message to log + * @param string $level The log level to use (debug, info, warning, error, critical) + * @param array $context [optional] The context to log extra information + * @return void + */ +function wh_log(string $message, string $level = 'info', array $context = []): void +{ + $formatter = new LineFormatter(null, null, true, true); + $stream = new StreamHandler(dirname(__FILE__, 3) . '../../storage/logs/installer.log', Logger::DEBUG); + $stream->setFormatter($formatter); + + $log = new Logger('CtrlPanel'); + $log->pushHandler($stream); + + switch (strtolower($level)) { + case 'debug': // Only log debug messages if APP_DEBUG is true + if (getenv('APP_DEBUG') === false) return; + $log->debug($message, $context); + break; + case 'info': + $log->info($message, $context); + break; + case 'warning': + $log->warning($message, $context); + break; + case 'error': + $log->error($message, $context); + break; + case 'critical': + $log->critical($message, $context); + break; + } + // Prevent memory leaks by resetting the logger + $log->reset(); +} + +?> diff --git a/public/installer/src/functions/shell.php b/public/installer/src/functions/shell.php index e69de29bb..c0ebb8e62 100644 --- a/public/installer/src/functions/shell.php +++ b/public/installer/src/functions/shell.php @@ -0,0 +1,41 @@ +<?php + +/** + * Run a shell command + * @param string $command The command string to run + * @param array|null $descriptors [optional]<p> + * An indexed array where the key represents the descriptor number and the value represents how PHP will pass that descriptor to the child process. 0 is stdin, 1 is stdout, while 2 is stderr. + * Default descriptors when null are 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] + * </p> + * @param string|null $cwd [optional] <p> + * The initial working dir for the command. This must be an + * absolute directory path, or null + * if you want to use the default value (the working dir of the current + * PHP process) + * </p> + * @param array|null $options [optional] <p> + * Allows you to specify additional options. + * @link https://www.php.net/manual/en/function.proc-open.php proc_open + * </p> + * @return false|string|null Returns the result from the command. + */ +function run_console(string $command, array $descriptors = null, string $cwd = null, array $options = null) +{ + wh_log('running command: ' . $command, 'debug'); + + $path = dirname(__FILE__, 3); + $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); + $output = stream_get_contents($pipes[1]); + $exit_code = proc_close($handle); + + if ($exit_code > 0) { + wh_log('command result: ' . $output, 'error'); + throw new Exception("There was an error after running command `$command`", $exit_code); + return $output; + } else { + return $output; + } +} + +?> diff --git a/public/installer/src/functions/utils.php b/public/installer/src/functions/utils.php index e69de29bb..381064027 100644 --- a/public/installer/src/functions/utils.php +++ b/public/installer/src/functions/utils.php @@ -0,0 +1,41 @@ +<?php + +function determineIfRunningInDocker(): bool +{ + return file_exists('/.dockerenv'); +} + +/** + * Encrypt the given value + * @param mixed $value The variable to be encrypted + * @param bool $serialize If the encryption should be serialized + * @return string Returns the encrypted variable. + */ +function encryptSettingsValue(mixed $value, $serialize = true): string +{ + $appKey = getenv('APP_KEY'); + $appKey = base64_decode(Str::after($appKey, 'base64:')); + $encrypter = new Encrypter($appKey, 'AES-256-CBC'); + $encryptedKey = $encrypter->encrypt($value, $serialize); + + return $encryptedKey; +} + +/** + * Decrypt the given value + * @param mixed $payload The payload to be decrypted + * @param bool $unserialize If the encryption should be unserialized + * @return mixed Returns the decrypted variable on success, throws otherwise. + */ + +function decryptSettingsValue(mixed $payload, $unserialize = true) +{ + $appKey = getenv('APP_KEY'); + $appKey = base64_decode(Str::after($appKey, 'base64:')); + $encrypter = new Encrypter($appKey, 'AES-256-CBC'); + $decryptedKey = $encrypter->decrypt($payload, $unserialize); + + return $decryptedKey; +} + +?> diff --git a/public/installer/views/admin-creation.php b/public/installer/views/admin-creation.php index b2eb9fa4f..19fe8c83e 100644 --- a/public/installer/views/admin-creation.php +++ b/public/installer/views/admin-creation.php @@ -6,10 +6,10 @@ $subtitle = "Lets create the first admin user!" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="createUser"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="createUser"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="form-group"> diff --git a/public/installer/views/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php index 642b0ddf5..76e6fc0b8 100644 --- a/public/installer/views/dashboard-configuration.php +++ b/public/installer/views/dashboard-configuration.php @@ -5,10 +5,10 @@ $title = "Dashboard Configuration" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkGeneral"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="checkGeneral"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/database-configuration.php b/public/installer/views/database-configuration.php index 5aa96dd20..623e92838 100644 --- a/public/installer/views/database-configuration.php +++ b/public/installer/views/database-configuration.php @@ -5,10 +5,10 @@ $title = "Database Configuration" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkDB"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="checkDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index cf12ba40c..cace87f77 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -6,10 +6,10 @@ $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="feedDB"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="feedDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="w-full flex justify-center"> diff --git a/public/installer/views/email-configuration.php b/public/installer/views/email-configuration.php index a7545fdae..195a4ad91 100644 --- a/public/installer/views/email-configuration.php +++ b/public/installer/views/email-configuration.php @@ -6,10 +6,10 @@ $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkSMTP"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="checkSMTP"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php index 47ca4acda..83100192f 100644 --- a/public/installer/views/pterodactyl-configuration.php +++ b/public/installer/views/pterodactyl-configuration.php @@ -6,10 +6,10 @@ $subtitle = "Lets get some info about your Pterodactyl Installation!" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkPtero"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="checkPtero"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index 76bc67380..24ad5d3b5 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -5,10 +5,10 @@ $title = "Redis Configuration" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="redisSetup"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="redisSetup"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index 0d2228773..4d57cfb1b 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -4,10 +4,10 @@ $title = "Timezone Configuration" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="timezoneConfig"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="timezoneConfig"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> From d08915d51fc683fc45e063b2ff1f96b27256ebe5 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 14 Jun 2024 00:00:19 +0200 Subject: [PATCH 411/514] refactor: refactored a lot of thing in the installer and added progress bar and next and previous button, and a footer and fixed some bugs --- public/installer/index.php | 71 +- public/installer/src/forms/admin.php | 1 + public/installer/src/forms/dashboard.php | 4 +- public/installer/src/forms/database.php | 2 - public/installer/src/forms/redis.php | 2 +- .../installer/src/functions/environment.php | 5 +- public/installer/src/functions/installer.php | 2 +- public/installer/src/functions/logging.php | 2 +- public/installer/src/functions/shell.php | 2 +- public/installer/src/functions/utils.php | 10 +- public/installer/styles.css | 968 +++++++++++++++++- public/installer/views/admin-creation.php | 27 +- .../views/dashboard-configuration.php | 27 +- .../views/database-configuration.php | 27 +- public/installer/views/database-migration.php | 27 +- .../installer/views/email-configuration.php | 31 +- .../installer/views/installation-complete.php | 2 +- public/installer/views/layout-bottom.php | 5 + public/installer/views/layout-top.php | 35 +- public/installer/views/mandatory-checks.php | 32 +- .../views/pterodactyl-configuration.php | 27 +- .../installer/views/redis-configuration.php | 27 +- .../views/timezone-configuration.php | 32 +- 23 files changed, 1271 insertions(+), 97 deletions(-) diff --git a/public/installer/index.php b/public/installer/index.php index 4feed33b6..a51c67455 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -1,4 +1,5 @@ <?php +// repport all error ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); @@ -6,17 +7,16 @@ session_start(); use DevCoder\DotEnv; -// use Illuminate\Encryption\Encrypter; -// use Illuminate\Support\Str; -require '../../vendor/autoload.php'; -require 'dotenv.php'; +// Include systems +require_once '../../vendor/autoload.php'; +require_once 'dotenv.php'; +require_once './src/functions/installer.php'; // Include the function files -require_once './src/functions/installer.php'; // very important +require_once './src/functions/logging.php'; require_once './src/functions/environment.php'; require_once './src/functions/shell.php'; -require_once './src/functions/logging.php'; require_once './src/functions/utils.php'; // Include the form files @@ -33,35 +33,52 @@ } if (!file_exists('../../.env')) { - echo run_console('cp .env.example .env'); + echo run_console('cp ../../.env.example ../../.env'); } +// load all the .env value in php env (new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); -$viewNames = [ - 1 => 'mandatory-checks', - 2 => 'timezone-configuration', - 3 => 'database-configuration', - 4 => 'database-migration', - 5 => 'redis-configuration', - 6 => 'dashboard-configuration', - 7 => 'email-configuration', - 8 => 'pterodactyl-configuration', - 9 => 'admin-creation', - 10 => 'installation-complete', +$stepConfig = [ + 1 => ['view' => 'mandatory-checks', 'is_revertable' => false], + 2 => ['view' => 'timezone-configuration', 'is_revertable' => true], + 3 => ['view' => 'database-configuration', 'is_revertable' => true], + 4 => ['view' => 'database-migration', 'is_revertable' => false], + 5 => ['view' => 'redis-configuration', 'is_revertable' => true], + 6 => ['view' => 'dashboard-configuration', 'is_revertable' => true], + 7 => ['view' => 'email-configuration', 'is_revertable' => true], + 8 => ['view' => 'pterodactyl-configuration', 'is_revertable' => false], + 9 => ['view' => 'admin-creation', 'is_revertable' => false], + 10 => ['view' => 'installation-complete', 'is_revertable' => false], ]; -// Prioritize $_GET['step'], then session, then default to 1 -$step = isset($_GET['step']) - ? (int)$_GET['step'] // Convert to integer for safety - : (isset($_SESSION['installation_step']) - ? $_SESSION['installation_step'] - : 1); +$_SESSION['last_installation_step'] = count($stepConfig); + +// Initialize or get the current step: +if (!isset($_SESSION['current_installation_step'])) { + // Session variable is not set, initialize it in the SESSION + $_SESSION['current_installation_step'] = 1; +} + +if (isset($_GET['step'])) { + $stepValue = $_GET['step']; + + if (is_numeric($stepValue) && $stepValue >= 1 && $stepValue <= $_SESSION['last_installation_step']) { + // Step is valid numeric within range: + $_SESSION['current_installation_step'] = $stepValue; + } elseif (strtolower($stepValue) === 'next' && $_SESSION['current_installation_step'] < $_SESSION['last_installation_step']) { + // Move to next step: + $_SESSION['current_installation_step']++; + } elseif (strtolower($stepValue) === 'previous' && $_SESSION['current_installation_step'] > 1) { + // Move to previous step: + $_SESSION['current_installation_step']--; + } +} -// Update session with the current step -$_SESSION['installation_step'] = $step; +$viewName = $stepConfig[$_SESSION['current_installation_step']]['view']; -$viewName = $viewNames[$step]; // Get the appropriate view name +// Set previous button availability based on step reversibility +$_SESSION['is_previous_button_available'] = $_SESSION['current_installation_step'] > 1 && $stepConfig[$_SESSION['current_installation_step'] - 1]['is_revertable']; // Load the layout and the specific view file include './views/layout-top.php'; diff --git a/public/installer/src/forms/admin.php b/public/installer/src/forms/admin.php index 9d0df6229..b909f9451 100644 --- a/public/installer/src/forms/admin.php +++ b/public/installer/src/forms/admin.php @@ -93,4 +93,5 @@ exit(); } } + ?> diff --git a/public/installer/src/forms/dashboard.php b/public/installer/src/forms/dashboard.php index 2229d4133..79804edd6 100644 --- a/public/installer/src/forms/dashboard.php +++ b/public/installer/src/forms/dashboard.php @@ -1,6 +1,5 @@ <?php - if (isset($_POST['checkGeneral'])) { wh_log('setting app settings', 'debug'); $appname = '"' . $_POST['name'] . '"'; @@ -26,4 +25,5 @@ wh_log('App settings set', 'debug'); next_step(); } -?> \ No newline at end of file + +?> diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index 8e0bdb615..24d5dc816 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -37,8 +37,6 @@ $logs = ''; try { - //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); - //$logs .= run_console('composer install --no-dev --optimize-autoloader'); if (!str_contains(getenv('APP_KEY'), 'base64')) { $logs .= run_console('php artisan key:generate --force'); } else { diff --git a/public/installer/src/forms/redis.php b/public/installer/src/forms/redis.php index 6837df3ae..3e9ece35a 100644 --- a/public/installer/src/forms/redis.php +++ b/public/installer/src/forms/redis.php @@ -1,6 +1,5 @@ <?php - if (isset($_POST['redisSetup'])) { wh_log('Setting up Redis', 'debug'); $redisHost = $_POST['redishost']; @@ -29,4 +28,5 @@ send_error_message("Please check your credentials!<br>" . $th->getMessage()); } } + ?> diff --git a/public/installer/src/functions/environment.php b/public/installer/src/functions/environment.php index 3ae262c39..c98223212 100644 --- a/public/installer/src/functions/environment.php +++ b/public/installer/src/functions/environment.php @@ -8,7 +8,9 @@ */ function setenv($envKey, $envValue) { - $envFile = dirname(__FILE__, 3) . '/.env'; + $rootDirectory = dirname(__DIR__, 4); + $envFile = $rootDirectory . '/.env'; + $str = file_get_contents($envFile); $str .= "\n"; // In case the searched variable is in the last line without \n @@ -23,7 +25,6 @@ function setenv($envKey, $envValue) fclose($fp); } - $required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl', 'redis']; /** diff --git a/public/installer/src/functions/installer.php b/public/installer/src/functions/installer.php index 24cbf5954..169931ea4 100644 --- a/public/installer/src/functions/installer.php +++ b/public/installer/src/functions/installer.php @@ -9,7 +9,7 @@ function send_error_message(string $message): void function next_step(): void { - $_SESSION['installation_step']++; + $_SESSION['current_installation_step']++; header("LOCATION: index.php"); } diff --git a/public/installer/src/functions/logging.php b/public/installer/src/functions/logging.php index f8151c4ec..3960ca11f 100644 --- a/public/installer/src/functions/logging.php +++ b/public/installer/src/functions/logging.php @@ -14,7 +14,7 @@ function wh_log(string $message, string $level = 'info', array $context = []): void { $formatter = new LineFormatter(null, null, true, true); - $stream = new StreamHandler(dirname(__FILE__, 3) . '../../storage/logs/installer.log', Logger::DEBUG); + $stream = new StreamHandler(dirname(__DIR__, 4) . '/storage/logs/installer.log', Logger::DEBUG); $stream->setFormatter($formatter); $log = new Logger('CtrlPanel'); diff --git a/public/installer/src/functions/shell.php b/public/installer/src/functions/shell.php index c0ebb8e62..7a72fcedf 100644 --- a/public/installer/src/functions/shell.php +++ b/public/installer/src/functions/shell.php @@ -23,7 +23,7 @@ function run_console(string $command, array $descriptors = null, string $cwd = n { wh_log('running command: ' . $command, 'debug'); - $path = dirname(__FILE__, 3); + $path = dirname(__DIR__, 4); $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); $output = stream_get_contents($pipes[1]); diff --git a/public/installer/src/functions/utils.php b/public/installer/src/functions/utils.php index 381064027..8f13bb69a 100644 --- a/public/installer/src/functions/utils.php +++ b/public/installer/src/functions/utils.php @@ -1,9 +1,6 @@ <?php -function determineIfRunningInDocker(): bool -{ - return file_exists('/.dockerenv'); -} +use Illuminate\Encryption\Encrypter; /** * Encrypt the given value @@ -38,4 +35,9 @@ function decryptSettingsValue(mixed $payload, $unserialize = true) return $decryptedKey; } +function determineIfRunningInDocker(): bool +{ + return file_exists('/.dockerenv'); +} + ?> diff --git a/public/installer/styles.css b/public/installer/styles.css index 22f5b596a..b7db03605 100644 --- a/public/installer/styles.css +++ b/public/installer/styles.css @@ -1 +1,967 @@ -/*! tailwindcss v3.3.1 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#94a3b8}input::placeholder,textarea::placeholder{opacity:1;color:#94a3b8}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.m-0{margin:0}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.box-border{box-sizing:border-box}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.w-1\/3{width:33.333333%}.w-full{width:100%}.min-w-fit{min-width:-moz-fit-content;min-width:fit-content}.list-none{list-style-type:none}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-around{justify-content:space-around}.gap-4{gap:1rem}.gap-8{gap:2rem}.rounded-2xl{border-radius:1rem}.rounded-md{border-radius:.375rem}.border-2{border-width:2px}.border-4{border-width:4px}.border-\[\#2E373B\]{--tw-border-opacity:1;border-color:rgb(46 55 59/var(--tw-border-opacity))}.border-transparent{border-color:#0000}.bg-\[\#1D2125\]{--tw-bg-opacity:1;background-color:rgb(29 33 37/var(--tw-bg-opacity))}.bg-\[\#242A2E\]{--tw-bg-opacity:1;background-color:rgb(36 42 46/var(--tw-bg-opacity))}.bg-green-500\/90{background-color:#22c55ee6}.bg-sky-500{--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity))}.bg-yellow-500\/90{background-color:#eab308e6}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pt-3{padding-top:.75rem}.text-center{text-align:center}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-neutral-400{--tw-text-opacity:1;color:rgb(163 163 163/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-green-400{--tw-shadow-color:#4ade80;--tw-shadow:var(--tw-shadow-colored)}.shadow-sky-400{--tw-shadow-color:#38bdf8;--tw-shadow:var(--tw-shadow-colored)}.shadow-yellow-400{--tw-shadow-color:#facc15;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid #0000;outline-offset:2px}.\[hostname\:port\]{hostname:port}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(22 163 74/var(--tw-bg-opacity))}.hover\:bg-sky-600:hover{--tw-bg-opacity:1;background-color:rgb(2 132 199/var(--tw-bg-opacity))}.hover\:bg-yellow-600:hover{--tw-bg-opacity:1;background-color:rgb(202 138 4/var(--tw-bg-opacity))}.focus\:border-sky-500:focus{--tw-border-opacity:1;border-color:rgb(14 165 233/var(--tw-border-opacity))}.focus\:outline:focus{outline-style:solid}.focus\:outline-2:focus{outline-width:2px}.focus\:outline-offset-2:focus{outline-offset:2px}.focus\:outline-green-500:focus{outline-color:#22c55e}.focus\:outline-sky-500:focus{outline-color:#0ea5e9}.focus\:outline-yellow-600:focus{outline-color:#ca8a04}@media (min-width:640px){.sm\:w-auto{width:auto}.sm\:min-w-\[550px\]{min-width:550px}} \ No newline at end of file +/* + * You need to have `tailwindcss` packages installed for these commands to work. + * + * Build: `tailwindcss -i tailwind_styles.css -o styles.css -m` + * Dev: `tailwindcss -i tailwind_styles.css -o styles.css --watch` + */ + +/* + ! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com + */ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e2e8f0; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #94a3b8; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #94a3b8; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.bottom-0 { + bottom: 0px; +} + +.m-0 { + margin: 0px; +} + +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.box-border { + box-sizing: border-box; +} + +.block { + display: block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.hidden { + display: none; +} + +.w-full { + width: 100%; +} + +.cursor-not-allowed { + cursor: not-allowed; +} + +.list-none { + list-style-type: none; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-4 { + gap: 1rem; +} + +.rounded-2xl { + border-radius: 1rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.border { + border-width: 1px; +} + +.border-2 { + border-width: 2px; +} + +.border-4 { + border-width: 4px; +} + +.border-\[\#2E373B\] { + --tw-border-opacity: 1; + border-color: rgb(46 55 59 / var(--tw-border-opacity)); +} + +.border-transparent { + border-color: transparent; +} + +.bg-\[\#1D2125\] { + --tw-bg-opacity: 1; + background-color: rgb(29 33 37 / var(--tw-bg-opacity)); +} + +.bg-\[\#242A2E\] { + --tw-bg-opacity: 1; + background-color: rgb(36 42 46 / var(--tw-bg-opacity)); +} + +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(30 41 59 / var(--tw-bg-opacity)); +} + +.bg-green-500\/90 { + background-color: rgb(34 197 94 / 0.9); +} + +.bg-red-300 { + --tw-bg-opacity: 1; + background-color: rgb(252 165 165 / var(--tw-bg-opacity)); +} + +.bg-sky-500 { + --tw-bg-opacity: 1; + background-color: rgb(14 165 233 / var(--tw-bg-opacity)); +} + +.bg-sky-600 { + --tw-bg-opacity: 1; + background-color: rgb(2 132 199 / var(--tw-bg-opacity)); +} + +.bg-yellow-500\/90 { + background-color: rgb(234 179 8 / 0.9); +} + +.bg-opacity-20 { + --tw-bg-opacity: 0.2; +} + +.p-0 { + padding: 0px; +} + +.p-0\.5 { + padding: 0.125rem; +} + +.p-6 { + padding: 1.5rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.pt-3 { + padding-top: 0.75rem; +} + +.text-center { + text-align: center; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.leading-none { + line-height: 1; +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(100 116 139 / var(--tw-text-opacity)); +} + +.text-neutral-400 { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} + +.text-sky-100 { + --tw-text-opacity: 1; + color: rgb(224 242 254 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.shadow-inner { + --tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-green-400 { + --tw-shadow-color: #4ade80; + --tw-shadow: var(--tw-shadow-colored); +} + +.shadow-red-200 { + --tw-shadow-color: #fecaca; + --tw-shadow: var(--tw-shadow-colored); +} + +.shadow-sky-400 { + --tw-shadow-color: #38bdf8; + --tw-shadow: var(--tw-shadow-colored); +} + +.shadow-yellow-400 { + --tw-shadow-color: #facc15; + --tw-shadow: var(--tw-shadow-colored); +} + +.outline-none { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.\[hostname\:port\] { + hostname: port; +} + +.hover\:bg-green-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.hover\:bg-red-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(248 113 113 / var(--tw-bg-opacity)); +} + +.hover\:bg-sky-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(2 132 199 / var(--tw-bg-opacity)); +} + +.hover\:bg-yellow-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(202 138 4 / var(--tw-bg-opacity)); +} + +.focus\:border-sky-500:focus { + --tw-border-opacity: 1; + border-color: rgb(14 165 233 / var(--tw-border-opacity)); +} + +.focus\:outline:focus { + outline-style: solid; +} + +.focus\:outline-2:focus { + outline-width: 2px; +} + +.focus\:outline-offset-2:focus { + outline-offset: 2px; +} + +.focus\:outline-green-500:focus { + outline-color: #22c55e; +} + +.focus\:outline-red-500:focus { + outline-color: #ef4444; +} + +.focus\:outline-sky-500:focus { + outline-color: #0ea5e9; +} + +.focus\:outline-yellow-600:focus { + outline-color: #ca8a04; +} + +@media (min-width: 640px) { + .sm\:w-auto { + width: auto; + } + + .sm\:min-w-\[550px\] { + min-width: 550px; + } +} \ No newline at end of file diff --git a/public/installer/views/admin-creation.php b/public/installer/views/admin-creation.php index 19fe8c83e..ef528555f 100644 --- a/public/installer/views/admin-creation.php +++ b/public/installer/views/admin-creation.php @@ -37,10 +37,29 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="createUser">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser"> + Next → </button> </div> </form> diff --git a/public/installer/views/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php index 76e6fc0b8..856f8df45 100644 --- a/public/installer/views/dashboard-configuration.php +++ b/public/installer/views/dashboard-configuration.php @@ -31,10 +31,29 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkGeneral">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral"> + Next → </button> </div> </form> diff --git a/public/installer/views/database-configuration.php b/public/installer/views/database-configuration.php index 623e92838..ca34a4a00 100644 --- a/public/installer/views/database-configuration.php +++ b/public/installer/views/database-configuration.php @@ -64,10 +64,29 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkDB">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next → </button> </div> </form> diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index cace87f77..9962c25c1 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -12,10 +12,29 @@ echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="feedDB">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB"> + Next → </button> </div> </form> diff --git a/public/installer/views/email-configuration.php b/public/installer/views/email-configuration.php index 195a4ad91..55ea14363 100644 --- a/public/installer/views/email-configuration.php +++ b/public/installer/views/email-configuration.php @@ -63,17 +63,36 @@ </div> </div> - <div class="flex w-full justify-around mt-4 gap-8 px-8"> - <button type="submit" - class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkSMTP">Submit - </button> + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> - <a href="?step=7" class="w-full"> + <a href="?step=next"> <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600"> Skip For Now </button> </a> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP"> + Next → + </button> </div> </form> diff --git a/public/installer/views/installation-complete.php b/public/installer/views/installation-complete.php index c9e04d23d..027784714 100644 --- a/public/installer/views/installation-complete.php +++ b/public/installer/views/installation-complete.php @@ -2,7 +2,7 @@ <!-- top layout here --> <?php -$lockfile = fopen('../../installer.lock', 'w') or exit('Unable to open file!'); +$lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); fwrite($lockfile, 'the installation is locked, delete this file to unlock it'); fclose($lockfile); diff --git a/public/installer/views/layout-bottom.php b/public/installer/views/layout-bottom.php index 35bfcba04..944ac0107 100644 --- a/public/installer/views/layout-bottom.php +++ b/public/installer/views/layout-bottom.php @@ -3,5 +3,10 @@ <!-- any middle view here --> + + <footer class="fixed bottom-0 w-full bg-gray-800 bg-opacity-20 text-center py-2 text-xs"> + © 2024 CtrlPanel | installer v2.0.0 + </footer> + </body> </html> diff --git a/public/installer/views/layout-top.php b/public/installer/views/layout-top.php index 3d2b89382..42003869b 100644 --- a/public/installer/views/layout-top.php +++ b/public/installer/views/layout-top.php @@ -42,18 +42,31 @@ </head> <body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> - <?php - function cardStart($title, $subtitle = null): string - { - return " - <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> - <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> - <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> - <h2 class='text-xl text-center mb-2'>$title</h2>" - . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); - } - ?> +<?php + + + function cardStart($title, $subtitle = null): string + { + // Get total number of steps (you'll need to define this) + $totalSteps = $_SESSION['last_installation_step']; // Assuming you have your $viewNames array defined + // Get current step from session (or default to 1 if not set) + $currentStep = $_SESSION['current_installation_step'] ?? 1; + + // Calculate progress percentage + $progressValue = round(($currentStep / $totalSteps) * 100); + + return " + <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> + <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> + <div class='border-2 border-[#2E373B] bg-[#242A2E] rounded-2xl mx-2'> + <div class='bg-sky-600 text-xs font-medium text-sky-100 text-center p-0.5 leading-none rounded-full' style='width: {$progressValue}%'>Step {$currentStep}</div> + </div> + <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> + <h2 class='text-xl text-center mb-2'>$title</h2>" + . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); +} +?> <!-- any middle view here --> <!-- bottom layout here --> diff --git a/public/installer/views/mandatory-checks.php b/public/installer/views/mandatory-checks.php index 039f378c4..4713c4581 100644 --- a/public/installer/views/mandatory-checks.php +++ b/public/installer/views/mandatory-checks.php @@ -44,11 +44,31 @@ </ul> -<a href="?step=2" class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> - Lets go! - </button> -</a> +<hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + +<div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + <a href="?step=next"> + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="mandatory"> + Next → + </button> + </a> +</div> <!-- bottom layout here --> diff --git a/public/installer/views/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php index 83100192f..5d74bd444 100644 --- a/public/installer/views/pterodactyl-configuration.php +++ b/public/installer/views/pterodactyl-configuration.php @@ -41,10 +41,29 @@ </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkPtero">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero"> + Next → </button> </div> </form> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index 24ad5d3b5..04e4586d3 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -40,10 +40,29 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="redisSetup">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="redisSetup"> + Next → </button> </div> </form> diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index 4d57cfb1b..7806c76b2 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -15,8 +15,7 @@ <div class="form-group"> <div class="flex flex-col mb-3"> <label for="timezone">Timezone</label> - <select id="timezone" name="timezone" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <select id="timezone" name="timezone" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> <?php foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { if ($timezoneIdentifier === 'UTC') { @@ -31,12 +30,31 @@ class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="timezoneConfig">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="timezoneConfig"> + Next → </button> </div> </form> -<!-- bottom layout here --> \ No newline at end of file +<!-- bottom layout here --> From d874b98d2498c0e3758f0bdba8a7924f8e8086dc Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 14 Jun 2024 00:06:04 +0200 Subject: [PATCH 412/514] docs: updated the .env.exemple --- .env.example | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index 46f1a273a..813110b54 100644 --- a/.env.example +++ b/.env.example @@ -1,25 +1,40 @@ +# ⚠️ CAUTION: Advanced Configuration ⚠️ +# +# This file (.env) stores sensitive environment variables. +# Modifying these values can significantly impact your application's behavior. +# +# Proceed with caution: +# - Only edit if you have a clear understanding of the specific variable and its purpose. +# - Use the control panel or installer for most configuration changes whenever possible. +# - Keep a backup of this file before making any modifications. +# +# Need Help? Consult the documentation or contact support before making changes. + ### --- App Settings --- ### APP_NAME=CtrlPanel.gg APP_ENV=production APP_KEY= APP_DEBUG=false APP_URL=http://localhost -APP_TIMEZONE=UTC # List with timezones https://www.php.net/manual/en/timezones.php +APP_TIMEZONE=UTC ### --- App Settings End --- ### ### --- Database Settings (required) --- ### +# SQL DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=dashboard DB_USERNAME=dashboarduser DB_PASSWORD= -### --- Database Settings End --- ### -### --- Google Recaptcha Settings --- ### -RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI -RECAPTCHA_SECRET_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe -### --- Google Recaptcha Settings End --- ### +# No-SQL +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 +### --- Database Settings End --- ### ### --- Mail Server Settings --- ### MAIL_MAILER=smtp @@ -55,15 +70,10 @@ PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 -### --- External Services Credentials End --- ### - -### --- Additional Configuration --- ### -MEMCACHED_HOST=127.0.0.1 - -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" -### --- Additional Configuration End --- ### + +RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI +RECAPTCHA_SECRET_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe +### --- External Services Credentials End --- ### From dbc12c1968aacaf7c7f87a716c414f058620b6d4 Mon Sep 17 00:00:00 2001 From: S0ly <86328249+S0ly@users.noreply.github.com> Date: Fri, 14 Jun 2024 00:28:20 +0200 Subject: [PATCH 413/514] fix: README.md --- README.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/README.md b/README.md index a3cc0e35d..291c86cff 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,6 @@ -<<<<<<< mercadopago-implementation -### Features - -- PayPal, Stripe, Mollie and MercadoPago Integration -- Hourly, Weekely, Monthly, Quarterly and Annual billing Cycles -- Referral System -- Partner System -- Ticket System -- Upgrade/Downgrade Server Resources -- Store (credit system with hourly billing and invoices) -- Email Verification -- Audit Log -- Admin Dashboard -- User/Server Management -- Customisable server plans -- Vouchers -- Alert System -- Theme Support -- and so much more! -======= <div align="center"> <img src="https://ctrlpanel.gg/img/controlpanel.png" width="128" alt="" /> </div> ->>>>>>> development # CtrlPanel-gg From d306a6b46913cff7dbdeacd226ea3c31c60913b1 Mon Sep 17 00:00:00 2001 From: S0ly <86328249+S0ly@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:12:31 +0200 Subject: [PATCH 414/514] docs: deleted discord link in the panel to prevent lost people from joining because of missconfiguration --- database/seeders/Seeds/UsefulLinksSeeder.php | 7 ------- themes/default/views/admin/overview/index.blade.php | 4 ---- 2 files changed, 11 deletions(-) diff --git a/database/seeders/Seeds/UsefulLinksSeeder.php b/database/seeders/Seeds/UsefulLinksSeeder.php index 03a8ce9dd..0d5f2b188 100644 --- a/database/seeders/Seeds/UsefulLinksSeeder.php +++ b/database/seeders/Seeds/UsefulLinksSeeder.php @@ -28,12 +28,5 @@ public function run() 'description' => 'View your database online using phpMyAdmin', 'position' => 'dashboard,topbar', ]); - UsefulLink::create([ - 'icon' => 'fab fa-discord', - 'title' => 'Discord', - 'link' => env('DISCORD_INVITE_URL', 'https://discord.gg/4Y6HjD2uyU'), - 'description' => 'Need a helping hand? Want to chat? Got any questions? Join our discord!', - 'position' => 'dashboard', - ]); } } diff --git a/themes/default/views/admin/overview/index.blade.php b/themes/default/views/admin/overview/index.blade.php index 09f07e93b..ab053f6bf 100644 --- a/themes/default/views/admin/overview/index.blade.php +++ b/themes/default/views/admin/overview/index.blade.php @@ -34,10 +34,6 @@ <div class="container-fluid"> <div class="mb-3 row"> - <div class="col-md-3"> - <a href="https://discord.gg/4Y6HjD2uyU" class="px-3 btn btn-dark btn-block"><i - class="mr-2 fab fa-discord"></i> {{__('Support server')}}</a> - </div> <div class="col-md-3"> <a href="https://CtrlPanel.gg/docs/intro" class="px-3 btn btn-dark btn-block"><i class="mr-2 fas fa-link"></i> {{__('Documentation')}}</a> From 3818b4e94608a672732c39f5bb61c29fa0a9eecf Mon Sep 17 00:00:00 2001 From: AGuyNamedJens <jens.wesseling@gmail.com> Date: Fri, 21 Jun 2024 22:46:38 +0200 Subject: [PATCH 415/514] [Bug] Fixed Discord Login Caveats: .env MUST contain DISCORD_CLIENT_ID and DISCORD_CLIENT_SECRET, otherwise insertion will still fail (NEEDS TO BE DOCUMENTED AS BREAKING!) --- .env.example | 6 ++++++ app/Http/Controllers/Admin/SettingsController.php | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 46f1a273a..7dbdbd6df 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,12 @@ DB_USERNAME=dashboarduser DB_PASSWORD= ### --- Database Settings End --- ### +### --- Discord Settings (required for Discord OAuth) --- ### +DISCORD_CLIENT_ID= +DISCORD_CLIENT_SECRET= + +### --- Discord Settings End --- ### + ### --- Google Recaptcha Settings --- ### RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI RECAPTCHA_SECRET_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 988603220..448a65683 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\View\View; use Illuminate\Http\Response; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Validator; use Qirolab\Theme\Theme; @@ -99,7 +100,7 @@ public function update(Request $request) $this->checkPermission("settings." . strtolower($category) . ".write"); - $settings_class = request()->get('settings_class'); + $settings_class = (string) request()->get('settings_class'); if (method_exists($settings_class, 'getValidations')) { $validations = $settings_class::getValidations(); @@ -120,6 +121,18 @@ public function update(Request $request) $rp = new \ReflectionProperty($settingsClass, $key); $rpType = $rp->getType(); + // Check if the settingsclass is a DiscordSettings class + if($settings_class == 'App\Settings\DiscordSettings') { + if($key === 'client_id' || $key === 'client_secret') { + + $env = file_get_contents(base_path('.env')); + $env = preg_replace('/DISCORD_CLIENT_ID=(.*)/', 'DISCORD_CLIENT_ID=' . $request->input('client_id'), $env); + $env = preg_replace('/DISCORD_CLIENT_SECRET=(.*)/', 'DISCORD_CLIENT_SECRET=' . $request->input('client_secret'), $env); + file_put_contents(base_path('.env'), $env); + + Artisan::call('config:clear'); + } + } if ($rpType == 'bool') { $settingsClass->$key = $request->has($key); From 1619d81e7bf9e0b8e571eb3fd75f7c5cd718309a Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 13 Jun 2024 13:12:33 +0200 Subject: [PATCH 416/514] Fix wrong php typehint --- app/Settings/TicketSettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Settings/TicketSettings.php b/app/Settings/TicketSettings.php index 89eec7270..4c7ab5a78 100644 --- a/app/Settings/TicketSettings.php +++ b/app/Settings/TicketSettings.php @@ -6,7 +6,7 @@ class TicketSettings extends Settings { - public bool $enabled; + public ?bool $enabled; public ?string $information; public static function group(): string From e52ab1f76b4a194872d33847c01c1cebfe6a11e7 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 13 Jun 2024 13:18:02 +0200 Subject: [PATCH 417/514] Fix trustedproxy deprecated default null value in explode function --- config/trustedproxy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/trustedproxy.php b/config/trustedproxy.php index dc46c31ba..f3b88cd52 100644 --- a/config/trustedproxy.php +++ b/config/trustedproxy.php @@ -26,7 +26,7 @@ * subsequently passed through. */ 'proxies' => in_array(env('TRUSTED_PROXIES', []), ['*', '**']) ? - env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', null)), + env('TRUSTED_PROXIES') : explode(',', env('TRUSTED_PROXIES', '')), /* * Or, to trust all proxies that connect From 51a2d490553d4632fbdb941f63806994bebf0463 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:31:43 +0200 Subject: [PATCH 418/514] Fix Installer migration failing to run because env encryption key is not loaded yet --- public/install/forms.php | 26 ++- public/install/index.php | 340 +++++++++++++++++++++------------------ 2 files changed, 200 insertions(+), 166 deletions(-) diff --git a/public/install/forms.php b/public/install/forms.php index 23f910efc..2fd18fe40 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -59,6 +59,25 @@ header('LOCATION: index.php?step=3.5'); } +if (isset($_POST['generateKey'])) { + wh_log('Feeding the Database', 'debug'); + + try { + if (!str_contains(getenv('APP_KEY'), 'base64')) { + $logs = run_console('php artisan key:generate --force'); + wh_log($logs, 'debug'); + + wh_log('Creating APP_KEY successful', 'debug'); + header('LOCATION: index.php?step=3.6'); + } else { + wh_log('Key already exists. Skipping', 'debug'); + } + } catch (Throwable $th) { + wh_log('Creating APP_KEY failed', 'error'); + header("LOCATION: index.php?step=3.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + } +} + if (isset($_POST['feedDB'])) { wh_log('Feeding the Database', 'debug'); $logs = ''; @@ -66,11 +85,6 @@ try { //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); //$logs .= run_console('composer install --no-dev --optimize-autoloader'); - if (!str_contains(getenv('APP_KEY'), 'base64')) { - $logs .= run_console('php artisan key:generate --force'); - } else { - $logs .= "Key already exists. Skipping\n"; - } $logs .= run_console('php artisan storage:link'); $logs .= run_console('php artisan migrate --seed --force'); $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); @@ -82,7 +96,7 @@ header('LOCATION: index.php?step=4'); } catch (Throwable $th) { wh_log('Feeding the Database failed', 'error'); - header("LOCATION: index.php?step=3.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + header("LOCATION: index.php?step=3.6&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); } } diff --git a/public/install/index.php b/public/install/index.php index 535411e45..629665423 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -64,189 +64,209 @@ function cardStart($title, $subtitle = null): string <?php -// Getting started -if (!isset($_GET['step']) || $_GET['step'] == 1) { - ?> - <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> - - <ul class="list-none mb-2"> - - <li class="<?php echo checkWriteable() ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> - - <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) - </li> - - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> - Missing php-extentions: - <?php echo count(checkExtensions()) == 0 ? 'none' : ''; - foreach (checkExtensions() as $ext) { - echo $ext . ', '; - } - echo count(checkExtensions()) === 0 ? '' : '(Proceed anyway)'; ?> - </li> - - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Git version: - <?php echo getGitVersion(); ?> - </li> - - <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Tar version: - <?php echo getTarVersion(); ?> - </li> - - <li> - <p class="text-neutral-400 mb-1"> - <br> - <span style="color: #eab308;">Important:</span> - CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> - Please make sure you have these installed and running before you continue. - </p> - </li> - - </ul> - - <a href="?step=2" class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> - Lets go! - </button> - </a> + // Getting started + if (!isset($_GET['step']) || $_GET['step'] == 1) { + ?> + <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> + + <ul class="list-none mb-2"> + + <li class="<?php echo checkWriteable() ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> + + <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) + </li> + + <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> + Missing php-extentions: + <?php echo count(checkExtensions()) == 0 ? 'none' : ''; + foreach (checkExtensions() as $ext) { + echo $ext . ', '; + } + echo count(checkExtensions()) === 0 ? '' : '(Proceed anyway)'; ?> + </li> + + <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Git version: + <?php echo getGitVersion(); ?> + </li> + + <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Tar version: + <?php echo getTarVersion(); ?> + </li> + + <li> + <p class="text-neutral-400 mb-1"> + <br> + <span style="color: #eab308;">Important:</span> + CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> + Please make sure you have these installed and running before you continue. + </p> + </li> + + </ul> + + <a href="?step=2" class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> + Lets go! + </button> + </a> - <?php -} + <?php + } + + // Timezone Config + if (isset($_GET['step']) && $_GET['step'] == 2) { + echo cardStart($title = "Timezone Configuration"); ?> + + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="timezoneConfig"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> -// Timezone Config -if (isset($_GET['step']) && $_GET['step'] == 2) { - echo cardStart($title = "Timezone Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="timezoneConfig"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="timezone">Timezone</label> - <select id="timezone" name="timezone" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <?php - foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { - if ($timezoneIdentifier === 'UTC') { - continue; - } - - echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; - } ?> - </select> + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="timezone">Timezone</label> + <select id="timezone" name="timezone" required + class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <?php + foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { + if ($timezoneIdentifier === 'UTC') { + continue; + } + + echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; + } ?> + </select> + </div> </div> </div> </div> - </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="timezoneConfig">Submit - </button> - </div> - </form> - <?php -} + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="timezoneConfig">Submit + </button> + </div> + </form> + <?php + } + + // DB Config + if (isset($_GET['step']) && $_GET['step'] == 3) { + echo cardStart($title = "Database Configuration"); ?> -// DB Config -if (isset($_GET['step']) && $_GET['step'] == 3) { - echo cardStart($title = "Database Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasedriver">Database Driver</label> - <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required - value="mysql" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasedriver">Database Driver</label> + <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required + value="mysql" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasehost">Database Host</label> - <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required - value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasehost">Database Host</label> + <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required + value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseport">Database Port</label> - <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required - value="3306" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseport">Database Port</label> + <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required + value="3306" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required - value="ctrlpaneluser" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuser">Database User</label> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required + value="ctrlpaneluser" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuserpass">Database User Password</label> - <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" - required - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuserpass">Database User Password</label> + <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" + required + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <div class="form-group"> + <div class="flex flex-col"> + <label for="database">Database</label> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> </div> </div> </div> - </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkDB">Submit - </button> - </div> - </form> + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkDB">Submit + </button> + </div> + </form> + + <?php + } + + // APP_KEY Generation + if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> + + <?php echo cardStart($title = "Encryption Key Generation", $subtitle = "Lets generate some security keys!"); ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="generateKey"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="generateKey">Submit + </button> + </div> + </form> <?php -} + } -// DB Migration & APP_KEY Generation -if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> + // DB Migration + if (isset($_GET['step']) && $_GET['step'] == 3.6) { ?> - <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> + <?php echo cardStart($title = "Database Migration", $subtitle = "Lets feed your Database! <br> This process might take a while. Please do not refresh or close this page!"); ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="feedDB">Submit - </button> - </div> - </form> + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="feedDB">Submit + </button> + </div> + </form> <?php } From 96b601e68599ae4b295739f3baafb2d3316bd546 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:34:03 +0200 Subject: [PATCH 419/514] Update wrong log message --- public/install/forms.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/install/forms.php b/public/install/forms.php index 2fd18fe40..5539f022b 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -60,14 +60,14 @@ } if (isset($_POST['generateKey'])) { - wh_log('Feeding the Database', 'debug'); + wh_log('Start APP_KEY generation', 'debug'); try { if (!str_contains(getenv('APP_KEY'), 'base64')) { $logs = run_console('php artisan key:generate --force'); wh_log($logs, 'debug'); - wh_log('Creating APP_KEY successful', 'debug'); + wh_log('Created APP_KEY successful', 'debug'); header('LOCATION: index.php?step=3.6'); } else { wh_log('Key already exists. Skipping', 'debug'); From b3d31244da0e9df4569cd16325f97b2e48d85c26 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 9 Jul 2024 20:16:20 +0200 Subject: [PATCH 420/514] Remove dedicated create APP_KEY form and move logic to DB config --- public/install/forms.php | 12 +++++------- public/install/index.php | 22 +--------------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/public/install/forms.php b/public/install/forms.php index 5539f022b..a6739343e 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -55,11 +55,6 @@ setenv($key, $param); } - wh_log('Database connection successful', 'debug'); - header('LOCATION: index.php?step=3.5'); -} - -if (isset($_POST['generateKey'])) { wh_log('Start APP_KEY generation', 'debug'); try { @@ -68,14 +63,17 @@ wh_log($logs, 'debug'); wh_log('Created APP_KEY successful', 'debug'); - header('LOCATION: index.php?step=3.6'); } else { wh_log('Key already exists. Skipping', 'debug'); } } catch (Throwable $th) { wh_log('Creating APP_KEY failed', 'error'); - header("LOCATION: index.php?step=3.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + header("LOCATION: index.php?step=3&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + exit(); } + + wh_log('Database connection successful', 'debug'); + header('LOCATION: index.php?step=3.5'); } if (isset($_POST['feedDB'])) { diff --git a/public/install/index.php b/public/install/index.php index 629665423..a4d78b0fa 100644 --- a/public/install/index.php +++ b/public/install/index.php @@ -230,28 +230,8 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s <?php } - // APP_KEY Generation - if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> - - <?php echo cardStart($title = "Encryption Key Generation", $subtitle = "Lets generate some security keys!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="generateKey"> - - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="generateKey">Submit - </button> - </div> - </form> - <?php - } - // DB Migration - if (isset($_GET['step']) && $_GET['step'] == 3.6) { ?> + if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> <?php echo cardStart($title = "Database Migration", $subtitle = "Lets feed your Database! <br> This process might take a while. Please do not refresh or close this page!"); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> From a67e0abf393937d6202eb2b1db376ae78752a9cb Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Tue, 9 Jul 2024 20:21:29 +0200 Subject: [PATCH 421/514] Fix wrong next step number --- public/install/forms.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/install/forms.php b/public/install/forms.php index a6739343e..8e4d5f49d 100644 --- a/public/install/forms.php +++ b/public/install/forms.php @@ -94,7 +94,7 @@ header('LOCATION: index.php?step=4'); } catch (Throwable $th) { wh_log('Feeding the Database failed', 'error'); - header("LOCATION: index.php?step=3.6&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + header("LOCATION: index.php?step=3.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); } } From d082ca103ec663cfdeef87bc3df8a6f9aec60b05 Mon Sep 17 00:00:00 2001 From: Drylian <109999325+drylian@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:46:16 -0300 Subject: [PATCH 422/514] add encrypted function --- .../PaymentGateways/MercadoPago/MercadoPagoSettings.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php index f9b2059f7..f543b6f50 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoSettings.php @@ -15,6 +15,13 @@ public static function group(): string return 'mercadopago'; } + public static function encrypted(): array + { + return [ + 'access_token' + ]; + } + public static function getOptionInputData() { return [ From 9772321a4d426f729cc90b2779ff8b783793339d Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Thu, 13 Jun 2024 13:12:33 +0200 Subject: [PATCH 423/514] Fix wrong php typehint --- app/Settings/TicketSettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Settings/TicketSettings.php b/app/Settings/TicketSettings.php index 89eec7270..4c7ab5a78 100644 --- a/app/Settings/TicketSettings.php +++ b/app/Settings/TicketSettings.php @@ -6,7 +6,7 @@ class TicketSettings extends Settings { - public bool $enabled; + public ?bool $enabled; public ?string $information; public static function group(): string From f72ffe451c79cd5ab90439bf8fd689522919e434 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:31:43 +0200 Subject: [PATCH 424/514] Fix Installer migration failing to run because env encryption key is not loaded yet --- public/installer/src/forms/database.php | 30 ++++++++++++++----- public/installer/views/database-migration.php | 4 +-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index 24d5dc816..5d4917070 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -19,17 +19,38 @@ $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); } catch (mysqli_sql_exception $e) { wh_log($e->getMessage(), 'error'); - send_error_message($e->getMessage()); + header('LOCATION: index.php?step=3&message=' . $e->getMessage()); exit(); } + foreach ($values as $key => $value) { $param = $_POST[$value]; + // if ($key == "DB_PASSWORD") { + // $param = '"' . $_POST[$value] . '"'; + // } setenv($key, $param); } + wh_log('Start APP_KEY generation', 'debug'); + + try { + if (!str_contains(getenv('APP_KEY'), 'base64')) { + $logs = run_console('php artisan key:generate --force'); + wh_log($logs, 'debug'); + + wh_log('Created APP_KEY successful', 'debug'); + } else { + wh_log('Key already exists. Skipping', 'debug'); + } + } catch (Throwable $th) { + wh_log('Creating APP_KEY failed', 'error'); + header("LOCATION: index.php?step=3&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + exit(); + } + wh_log('Database connection successful', 'debug'); - next_step(); + header('LOCATION: index.php?step=3.5'); } if (isset($_POST['feedDB'])) { @@ -37,11 +58,6 @@ $logs = ''; try { - if (!str_contains(getenv('APP_KEY'), 'base64')) { - $logs .= run_console('php artisan key:generate --force'); - } else { - $logs .= "Key already exists. Skipping\n"; - } $logs .= run_console('php artisan storage:link'); $logs .= run_console('php artisan migrate --seed --force'); $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index 9962c25c1..85f0e6779 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -2,8 +2,8 @@ <!-- top layout here --> <?php echo cardStart( - $title = "Database Migration and Encryption Key Generation", - $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!" + $title = "Database Migration", + $subtitle = "Lets feed your Database! <br> This process might take a while. Please do not refresh or close this page!" ); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="feedDB"> From 0558118d65458e6616cb52cd15c40196c525f7d2 Mon Sep 17 00:00:00 2001 From: S0ly <86328249+S0ly@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:24:40 +0200 Subject: [PATCH 425/514] fix: conflicts fix --- public/installer/src/forms/database.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index 5d4917070..f474adfdf 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -19,16 +19,12 @@ $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); } catch (mysqli_sql_exception $e) { wh_log($e->getMessage(), 'error'); - header('LOCATION: index.php?step=3&message=' . $e->getMessage()); + send_error_message($e->getMessage()); exit(); } - foreach ($values as $key => $value) { $param = $_POST[$value]; - // if ($key == "DB_PASSWORD") { - // $param = '"' . $_POST[$value] . '"'; - // } setenv($key, $param); } @@ -50,7 +46,7 @@ } wh_log('Database connection successful', 'debug'); - header('LOCATION: index.php?step=3.5'); + next_step(); } if (isset($_POST['feedDB'])) { From bf1247d507faafd9b88fad362b3060fe75f78579 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 10:17:53 +0200 Subject: [PATCH 426/514] refactor: renamed install into installer and started to split the files --- app/Http/Middleware/InstallerLock.php | 2 +- public/{install => installer}/dotenv.php | 0 public/{install => installer}/forms.php | 0 public/{install => installer}/functions.php | 0 public/{install => installer}/index.php | 338 +++++++++--------- .../phpmailer/Exception.php | 0 .../phpmailer/PHPMailer.php | 0 .../{install => installer}/phpmailer/SMTP.php | 0 public/installer/src/forms/admin.php | 0 public/installer/src/forms/dashboard.php | 0 public/installer/src/forms/database.php | 0 public/installer/src/forms/pterodactyl.php | 0 public/installer/src/forms/redis.php | 0 public/installer/src/forms/smtp.php | 0 public/installer/src/forms/timezone.php | 0 public/installer/src/functions/database.php | 0 .../installer/src/functions/environment.php | 0 public/installer/src/functions/logging.php | 0 public/installer/src/functions/shell.php | 0 public/installer/src/functions/utils.php | 0 public/{install => installer}/styles.css | 0 .../{install => installer}/tailwind.config.js | 0 .../tailwind_styles.css | 0 public/installer/view/admin-creation.php | 0 .../view/dashboard-configuration.php | 0 .../installer/view/database-configuration.php | 0 public/installer/view/database-migration.php | 0 public/installer/view/email-configuration.php | 0 .../installer/view/installation-complete.php | 0 public/installer/view/mandatory-checks.php | 0 .../view/pterodactyl-configuration.php | 0 public/installer/view/redis-configuration.php | 0 .../installer/view/timezone-configuration.php | 0 33 files changed, 170 insertions(+), 170 deletions(-) rename public/{install => installer}/dotenv.php (100%) rename public/{install => installer}/forms.php (100%) rename public/{install => installer}/functions.php (100%) rename public/{install => installer}/index.php (65%) rename public/{install => installer}/phpmailer/Exception.php (100%) rename public/{install => installer}/phpmailer/PHPMailer.php (100%) rename public/{install => installer}/phpmailer/SMTP.php (100%) create mode 100644 public/installer/src/forms/admin.php create mode 100644 public/installer/src/forms/dashboard.php create mode 100644 public/installer/src/forms/database.php create mode 100644 public/installer/src/forms/pterodactyl.php create mode 100644 public/installer/src/forms/redis.php create mode 100644 public/installer/src/forms/smtp.php create mode 100644 public/installer/src/forms/timezone.php create mode 100644 public/installer/src/functions/database.php create mode 100644 public/installer/src/functions/environment.php create mode 100644 public/installer/src/functions/logging.php create mode 100644 public/installer/src/functions/shell.php create mode 100644 public/installer/src/functions/utils.php rename public/{install => installer}/styles.css (100%) rename public/{install => installer}/tailwind.config.js (100%) rename public/{install => installer}/tailwind_styles.css (100%) create mode 100644 public/installer/view/admin-creation.php create mode 100644 public/installer/view/dashboard-configuration.php create mode 100644 public/installer/view/database-configuration.php create mode 100644 public/installer/view/database-migration.php create mode 100644 public/installer/view/email-configuration.php create mode 100644 public/installer/view/installation-complete.php create mode 100644 public/installer/view/mandatory-checks.php create mode 100644 public/installer/view/pterodactyl-configuration.php create mode 100644 public/installer/view/redis-configuration.php create mode 100644 public/installer/view/timezone-configuration.php diff --git a/app/Http/Middleware/InstallerLock.php b/app/Http/Middleware/InstallerLock.php index 9390a598c..f6f09e3b5 100644 --- a/app/Http/Middleware/InstallerLock.php +++ b/app/Http/Middleware/InstallerLock.php @@ -17,7 +17,7 @@ class InstallerLock public function handle(Request $request, Closure $next) { if (!file_exists(base_path()."/install.lock")){ - return redirect('/install'); + return redirect('/installer'); } return $next($request); } diff --git a/public/install/dotenv.php b/public/installer/dotenv.php similarity index 100% rename from public/install/dotenv.php rename to public/installer/dotenv.php diff --git a/public/install/forms.php b/public/installer/forms.php similarity index 100% rename from public/install/forms.php rename to public/installer/forms.php diff --git a/public/install/functions.php b/public/installer/functions.php similarity index 100% rename from public/install/functions.php rename to public/installer/functions.php diff --git a/public/install/index.php b/public/installer/index.php similarity index 65% rename from public/install/index.php rename to public/installer/index.php index a4d78b0fa..5d73c2e2a 100644 --- a/public/install/index.php +++ b/public/installer/index.php @@ -1,7 +1,7 @@ <?php include 'functions.php'; -if (file_exists('../../install.lock')) { +if (file_exists('../../installer.lock')) { exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } @@ -21,7 +21,7 @@ function cardStart($title, $subtitle = null): string <head> <title>CtrlPanel.gg installer Script</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link href="/install/styles.css" rel="stylesheet"> + <link href="/installer/styles.css" rel="stylesheet"> <style> body { color-scheme: dark; @@ -64,189 +64,189 @@ function cardStart($title, $subtitle = null): string <?php - // Getting started - if (!isset($_GET['step']) || $_GET['step'] == 1) { - ?> - <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> - - <ul class="list-none mb-2"> - - <li class="<?php echo checkWriteable() ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> - - <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) - </li> - - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> - Missing php-extentions: - <?php echo count(checkExtensions()) == 0 ? 'none' : ''; - foreach (checkExtensions() as $ext) { - echo $ext . ', '; - } - echo count(checkExtensions()) === 0 ? '' : '(Proceed anyway)'; ?> - </li> - - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Git version: - <?php echo getGitVersion(); ?> - </li> - - <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Tar version: - <?php echo getTarVersion(); ?> - </li> - - <li> - <p class="text-neutral-400 mb-1"> - <br> - <span style="color: #eab308;">Important:</span> - CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> - Please make sure you have these installed and running before you continue. - </p> - </li> - - </ul> - - <a href="?step=2" class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> - Lets go! - </button> - </a> - - <?php - } - - // Timezone Config - if (isset($_GET['step']) && $_GET['step'] == 2) { - echo cardStart($title = "Timezone Configuration"); ?> +// Getting started +if (!isset($_GET['step']) || $_GET['step'] == 1) { + ?> + <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> + + <ul class="list-none mb-2"> + + <li class="<?php echo checkWriteable() ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> + + <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) + </li> + + <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> + Missing php-extentions: + <?php echo count(checkExtensions()) == 0 ? 'none' : ''; + foreach (checkExtensions() as $ext) { + echo $ext . ', '; + } + echo count(checkExtensions()) === 0 ? '' : '(Proceed anyway)'; ?> + </li> + + <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Git version: + <?php echo getGitVersion(); ?> + </li> + + <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Tar version: + <?php echo getTarVersion(); ?> + </li> + + <li> + <p class="text-neutral-400 mb-1"> + <br> + <span style="color: #eab308;">Important:</span> + CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> + Please make sure you have these installed and running before you continue. + </p> + </li> + + </ul> + + <a href="?step=2" class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> + Lets go! + </button> + </a> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="timezoneConfig"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php +} - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="timezone">Timezone</label> - <select id="timezone" name="timezone" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <?php - foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { - if ($timezoneIdentifier === 'UTC') { - continue; - } - - echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; - } ?> - </select> - </div> +// Timezone Config +if (isset($_GET['step']) && $_GET['step'] == 2) { + echo cardStart($title = "Timezone Configuration"); ?> + + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="timezoneConfig"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="timezone">Timezone</label> + <select id="timezone" name="timezone" required + class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <?php + foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { + if ($timezoneIdentifier === 'UTC') { + continue; + } + + echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; + } ?> + </select> </div> </div> </div> + </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="timezoneConfig">Submit - </button> - </div> - </form> - <?php - } - - // DB Config - if (isset($_GET['step']) && $_GET['step'] == 3) { - echo cardStart($title = "Database Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="timezoneConfig">Submit + </button> + </div> + </form> + <?php +} - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasedriver">Database Driver</label> - <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required - value="mysql" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> +// DB Config +if (isset($_GET['step']) && $_GET['step'] == 3) { + echo cardStart($title = "Database Configuration"); ?> + + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkDB"> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasedriver">Database Driver</label> + <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required + value="mysql" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasehost">Database Host</label> - <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required - value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasehost">Database Host</label> + <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required + value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseport">Database Port</label> - <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required - value="3306" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseport">Database Port</label> + <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required + value="3306" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required - value="ctrlpaneluser" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuser">Database User</label> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required + value="ctrlpaneluser" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuserpass">Database User Password</label> - <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" - required - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuserpass">Database User Password</label> + <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" + required + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> + </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="database">Database</label> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> </div> + </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkDB">Submit - </button> - </div> - </form> + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkDB">Submit + </button> + </div> + </form> - <?php - } + <?php +} - // DB Migration - if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> +// DB Migration & APP_KEY Generation +if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> - <?php echo cardStart($title = "Database Migration", $subtitle = "Lets feed your Database! <br> This process might take a while. Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="feedDB"> + <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="feedDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="feedDB">Submit - </button> - </div> - </form> + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="feedDB">Submit + </button> + </div> + </form> <?php } @@ -254,7 +254,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 4) { echo cardStart($title = "Redis Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="redisSetup"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="redisSetup"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -303,7 +303,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 5) { echo cardStart($title = "Dashboard Configuration"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkGeneral"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkGeneral"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; @@ -346,7 +346,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 6) { echo cardStart($title = "E-Mail Configuration", $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkSMTP"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkSMTP"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -433,7 +433,7 @@ class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-60 if (isset($_GET['step']) && $_GET['step'] == 7) { echo cardStart($title = "Pterodactyl Configuration", $subtitle = "Lets get some info about your Pterodactyl Installation!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="checkPtero"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkPtero"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; } ?> @@ -484,7 +484,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s if (isset($_GET['step']) && $_GET['step'] == 8) { echo cardStart($title = "First Admin Creation", $subtitle = "Lets create the first admin user!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/install/forms.php" name="createUser"> + <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="createUser"> <?php if (isset($_GET['message'])) { echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; @@ -530,7 +530,7 @@ class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-s // Install Finished if (isset($_GET['step']) && $_GET['step'] == 9) { - $lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); + $lockfile = fopen('../../installer.lock', 'w') or exit('Unable to open file!'); fwrite($lockfile, 'locked'); fclose($lockfile); diff --git a/public/install/phpmailer/Exception.php b/public/installer/phpmailer/Exception.php similarity index 100% rename from public/install/phpmailer/Exception.php rename to public/installer/phpmailer/Exception.php diff --git a/public/install/phpmailer/PHPMailer.php b/public/installer/phpmailer/PHPMailer.php similarity index 100% rename from public/install/phpmailer/PHPMailer.php rename to public/installer/phpmailer/PHPMailer.php diff --git a/public/install/phpmailer/SMTP.php b/public/installer/phpmailer/SMTP.php similarity index 100% rename from public/install/phpmailer/SMTP.php rename to public/installer/phpmailer/SMTP.php diff --git a/public/installer/src/forms/admin.php b/public/installer/src/forms/admin.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/dashboard.php b/public/installer/src/forms/dashboard.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/pterodactyl.php b/public/installer/src/forms/pterodactyl.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/redis.php b/public/installer/src/forms/redis.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/smtp.php b/public/installer/src/forms/smtp.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/forms/timezone.php b/public/installer/src/forms/timezone.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/database.php b/public/installer/src/functions/database.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/environment.php b/public/installer/src/functions/environment.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/logging.php b/public/installer/src/functions/logging.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/shell.php b/public/installer/src/functions/shell.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/src/functions/utils.php b/public/installer/src/functions/utils.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/install/styles.css b/public/installer/styles.css similarity index 100% rename from public/install/styles.css rename to public/installer/styles.css diff --git a/public/install/tailwind.config.js b/public/installer/tailwind.config.js similarity index 100% rename from public/install/tailwind.config.js rename to public/installer/tailwind.config.js diff --git a/public/install/tailwind_styles.css b/public/installer/tailwind_styles.css similarity index 100% rename from public/install/tailwind_styles.css rename to public/installer/tailwind_styles.css diff --git a/public/installer/view/admin-creation.php b/public/installer/view/admin-creation.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/dashboard-configuration.php b/public/installer/view/dashboard-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/database-configuration.php b/public/installer/view/database-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/database-migration.php b/public/installer/view/database-migration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/email-configuration.php b/public/installer/view/email-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/installation-complete.php b/public/installer/view/installation-complete.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/mandatory-checks.php b/public/installer/view/mandatory-checks.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/pterodactyl-configuration.php b/public/installer/view/pterodactyl-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/redis-configuration.php b/public/installer/view/redis-configuration.php new file mode 100644 index 000000000..e69de29bb diff --git a/public/installer/view/timezone-configuration.php b/public/installer/view/timezone-configuration.php new file mode 100644 index 000000000..e69de29bb From cf7912dc132bce71a96543d756b212e32a296fa3 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 10:26:48 +0200 Subject: [PATCH 427/514] fix: renamed view to views folder in installer --- public/installer/{view => views}/admin-creation.php | 0 public/installer/{view => views}/dashboard-configuration.php | 0 public/installer/{view => views}/database-configuration.php | 0 public/installer/{view => views}/database-migration.php | 0 public/installer/{view => views}/email-configuration.php | 0 public/installer/{view => views}/installation-complete.php | 0 public/installer/{view => views}/mandatory-checks.php | 0 public/installer/{view => views}/pterodactyl-configuration.php | 0 public/installer/{view => views}/redis-configuration.php | 0 public/installer/{view => views}/timezone-configuration.php | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename public/installer/{view => views}/admin-creation.php (100%) rename public/installer/{view => views}/dashboard-configuration.php (100%) rename public/installer/{view => views}/database-configuration.php (100%) rename public/installer/{view => views}/database-migration.php (100%) rename public/installer/{view => views}/email-configuration.php (100%) rename public/installer/{view => views}/installation-complete.php (100%) rename public/installer/{view => views}/mandatory-checks.php (100%) rename public/installer/{view => views}/pterodactyl-configuration.php (100%) rename public/installer/{view => views}/redis-configuration.php (100%) rename public/installer/{view => views}/timezone-configuration.php (100%) diff --git a/public/installer/view/admin-creation.php b/public/installer/views/admin-creation.php similarity index 100% rename from public/installer/view/admin-creation.php rename to public/installer/views/admin-creation.php diff --git a/public/installer/view/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php similarity index 100% rename from public/installer/view/dashboard-configuration.php rename to public/installer/views/dashboard-configuration.php diff --git a/public/installer/view/database-configuration.php b/public/installer/views/database-configuration.php similarity index 100% rename from public/installer/view/database-configuration.php rename to public/installer/views/database-configuration.php diff --git a/public/installer/view/database-migration.php b/public/installer/views/database-migration.php similarity index 100% rename from public/installer/view/database-migration.php rename to public/installer/views/database-migration.php diff --git a/public/installer/view/email-configuration.php b/public/installer/views/email-configuration.php similarity index 100% rename from public/installer/view/email-configuration.php rename to public/installer/views/email-configuration.php diff --git a/public/installer/view/installation-complete.php b/public/installer/views/installation-complete.php similarity index 100% rename from public/installer/view/installation-complete.php rename to public/installer/views/installation-complete.php diff --git a/public/installer/view/mandatory-checks.php b/public/installer/views/mandatory-checks.php similarity index 100% rename from public/installer/view/mandatory-checks.php rename to public/installer/views/mandatory-checks.php diff --git a/public/installer/view/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php similarity index 100% rename from public/installer/view/pterodactyl-configuration.php rename to public/installer/views/pterodactyl-configuration.php diff --git a/public/installer/view/redis-configuration.php b/public/installer/views/redis-configuration.php similarity index 100% rename from public/installer/view/redis-configuration.php rename to public/installer/views/redis-configuration.php diff --git a/public/installer/view/timezone-configuration.php b/public/installer/views/timezone-configuration.php similarity index 100% rename from public/installer/view/timezone-configuration.php rename to public/installer/views/timezone-configuration.php From da59b8fc199ab72f8a02614fa2eb699000eecb09 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 11:16:27 +0200 Subject: [PATCH 428/514] refactor: moved mailer files of installer in src --- public/installer/{ => src}/phpmailer/Exception.php | 0 public/installer/{ => src}/phpmailer/PHPMailer.php | 0 public/installer/{ => src}/phpmailer/SMTP.php | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename public/installer/{ => src}/phpmailer/Exception.php (100%) rename public/installer/{ => src}/phpmailer/PHPMailer.php (100%) rename public/installer/{ => src}/phpmailer/SMTP.php (100%) diff --git a/public/installer/phpmailer/Exception.php b/public/installer/src/phpmailer/Exception.php similarity index 100% rename from public/installer/phpmailer/Exception.php rename to public/installer/src/phpmailer/Exception.php diff --git a/public/installer/phpmailer/PHPMailer.php b/public/installer/src/phpmailer/PHPMailer.php similarity index 100% rename from public/installer/phpmailer/PHPMailer.php rename to public/installer/src/phpmailer/PHPMailer.php diff --git a/public/installer/phpmailer/SMTP.php b/public/installer/src/phpmailer/SMTP.php similarity index 100% rename from public/installer/phpmailer/SMTP.php rename to public/installer/src/phpmailer/SMTP.php From 98a3e388925b82af5e17e11973a91019db20e8c5 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 11:16:53 +0200 Subject: [PATCH 429/514] refactor: refactored views of installer --- config/database.php | 2 +- public/installer/forms.php | 8 +- public/installer/index.php | 588 ++---------------- public/installer/views/admin-creation.php | 48 ++ .../views/dashboard-configuration.php | 42 ++ .../views/database-configuration.php | 75 +++ public/installer/views/database-migration.php | 23 + .../installer/views/email-configuration.php | 80 +++ .../installer/views/installation-complete.php | 22 + public/installer/views/layout-bottom.php | 7 + public/installer/views/layout-top.php | 59 ++ public/installer/views/mandatory-checks.php | 54 ++ .../views/pterodactyl-configuration.php | 52 ++ .../installer/views/redis-configuration.php | 51 ++ .../views/timezone-configuration.php | 42 ++ 15 files changed, 601 insertions(+), 552 deletions(-) create mode 100644 public/installer/views/layout-bottom.php create mode 100644 public/installer/views/layout-top.php diff --git a/config/database.php b/config/database.php index dc722b5fd..1917bc6a7 100644 --- a/config/database.php +++ b/config/database.php @@ -119,7 +119,7 @@ 'redis' => [ - 'client' => env('REDIS_CLIENT', 'phpredis'), + 'client' => env('REDIS_CLIENT', 'predis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), diff --git a/public/installer/forms.php b/public/installer/forms.php index 8e4d5f49d..6c8795a13 100644 --- a/public/installer/forms.php +++ b/public/installer/forms.php @@ -7,11 +7,9 @@ use PHPMailer\PHPMailer\PHPMailer; use Predis\Client; -require 'phpmailer/Exception.php'; -require 'phpmailer/PHPMailer.php'; -require 'phpmailer/SMTP.php'; - -include 'functions.php'; +require './src/phpmailer/Exception.php'; +require './src/phpmailer/PHPMailer.php'; +require './src/phpmailer/SMTP.php'; if (isset($_POST['timezoneConfig'])) { wh_log('Setting up Timezone', 'debug'); diff --git a/public/installer/index.php b/public/installer/index.php index 5d73c2e2a..24ee47bbd 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -1,551 +1,47 @@ <?php -include 'functions.php'; -if (file_exists('../../installer.lock')) { +// Include the function files +require_once './src/functions/environment.php'; +require_once './src/functions/database.php'; +require_once './src/functions/shell.php'; +require_once './src/functions/logging.php'; +require_once './src/functions/utils.php'; + +// Include the form files +require_once './src/forms/timezone.php'; +require_once './src/forms/database.php'; +require_once './src/forms/redis.php'; +require_once './src/forms/dashboard.php'; +require_once './src/forms/smtp.php'; +require_once './src/forms/pterodactyl.php'; +require_once './src/forms/admin.php'; + +require_once './functions.php'; +require_once './forms.php'; + +if (file_exists('../../install.lock')) { exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } -function cardStart($title, $subtitle = null): string -{ - return " - <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> - <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> - <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> - <h2 class='text-xl text-center mb-2'>$title</h2>" - . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); -} - -?> - -<html lang="en"> -<head> - <title>CtrlPanel.gg installer Script</title> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <link href="/installer/styles.css" rel="stylesheet"> - <style> - body { - color-scheme: dark; - } - - .check { - display: flex; - gap: 5px; - align-items: center; - margin-bottom: 5px; - } - - .check::before { - width: 20px; - height: 20px; - display: block; - } - - .ok { - color: lightgreen; - } - - /* Green Checkmark */ - .ok::before { - content: url("data:image/svg+xml,%3Csvg fill='none' stroke='lightgreen' stroke-width='1.5' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); - } - - .not-ok { - color: lightcoral; - } - - /* Red Cross */ - .not-ok::before { - content: url("data:image/svg+xml,%3Csvg fill='none' stroke='lightcoral' stroke-width='1.5' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); - } - </style> -</head> - -<body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> - -<?php - -// Getting started -if (!isset($_GET['step']) || $_GET['step'] == 1) { - ?> - <?php echo cardStart($title = "Mandatory Checks before Installation", $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup"); ?> - - <ul class="list-none mb-2"> - - <li class="<?php echo checkWriteable() ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> - - <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) - </li> - - <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> - Missing php-extentions: - <?php echo count(checkExtensions()) == 0 ? 'none' : ''; - foreach (checkExtensions() as $ext) { - echo $ext . ', '; - } - echo count(checkExtensions()) === 0 ? '' : '(Proceed anyway)'; ?> - </li> - - <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Git version: - <?php echo getGitVersion(); ?> - </li> - - <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> - Tar version: - <?php echo getTarVersion(); ?> - </li> - - <li> - <p class="text-neutral-400 mb-1"> - <br> - <span style="color: #eab308;">Important:</span> - CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> - Please make sure you have these installed and running before you continue. - </p> - </li> - - </ul> - - <a href="?step=2" class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> - Lets go! - </button> - </a> - - <?php -} - -// Timezone Config -if (isset($_GET['step']) && $_GET['step'] == 2) { - echo cardStart($title = "Timezone Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="timezoneConfig"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="timezone">Timezone</label> - <select id="timezone" name="timezone" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <?php - foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { - if ($timezoneIdentifier === 'UTC') { - continue; - } - - echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; - } ?> - </select> - </div> - </div> - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="timezoneConfig">Submit - </button> - </div> - </form> - <?php -} - -// DB Config -if (isset($_GET['step']) && $_GET['step'] == 3) { - echo cardStart($title = "Database Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasedriver">Database Driver</label> - <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required - value="mysql" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databasehost">Database Host</label> - <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required - value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseport">Database Port</label> - <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required - value="3306" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuser">Database User</label> - <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required - value="ctrlpaneluser" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="databaseuserpass">Database User Password</label> - <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" - required - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col"> - <label for="database">Database</label> - <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkDB">Submit - </button> - </div> - </form> - - <?php -} - -// DB Migration & APP_KEY Generation -if (isset($_GET['step']) && $_GET['step'] == 3.5) { ?> - - <?php echo cardStart($title = "Database Migration and Encryption Key Generation", $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!"); ?> - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="feedDB"> - - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="feedDB">Submit - </button> - </div> - </form> - <?php - } - - // Redis Config - if (isset($_GET['step']) && $_GET['step'] == 4) { - echo cardStart($title = "Redis Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="redisSetup"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="redishost">Redis Host</label> - <input x-model="redishost" id="redishost" name="redishost" type="text" required - value="<?php echo(determineIfRunningInDocker() ? 'redis' : '127.0.0.1') ?>" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="redisport">Redis Port</label> - <input x-model="redisport" id="redisport" name="redisport" type="number" required - value="6379" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="redispassword">Redis Password (optionally, only if configured)</label> - <input x-model="redispassword" id="redispassword" name="redispassword" type="text" - placeholder="usually can be left blank" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="redisSetup">Submit - </button> - </div> - </form> - - <?php - } - - // Dashboard Config - if (isset($_GET['step']) && $_GET['step'] == 5) { - echo cardStart($title = "Dashboard Configuration"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkGeneral"> - - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="database">Dashboard URL</label> - <input id="url" name="url" type="text" required - value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="name">Dashboard Name</label> - <input id="name" name="name" type="text" required value="CtrlPanel" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkGeneral">Submit - </button> - </div> - </form> - - - <?php - } - - // Email Config - if (isset($_GET['step']) && $_GET['step'] == 6) { - echo cardStart($title = "E-Mail Configuration", $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkSMTP"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="method">E-Mail Protocol</label> - <select id="method" name="method" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="smtp" selected>SMTP</option> - </select> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="host">Your Mailer-Host</label> - <input id="host" name="host" type="text" required value="smtp.google.com" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="port">Your Mail Port</label> - <input id="port" name="port" type="number" required value="567" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="user">Your Mail User</label> - <input id="user" name="user" type="text" required value="info@mydomain.com" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Your Mail-User Password</label> - <input id="pass" name="pass" type="password" required value="" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col"> - <label for="encryption">Your Mail encryption method</label> - <select id="encryption" name="encryption" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <option value="tls" selected>TLS</option> - <option value="ssl">SSL</option> - <option value="null">None</option> - </select> - </div> - </div> - </div> - </div> - - - <div class="flex w-full justify-around mt-4 gap-8 px-8"> - <button type="submit" - class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkSMTP">Submit - </button> - - <a href="?step=7" class="w-full"> - <button type="button" - class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600"> - Skip For Now - </button> - </a> - </div> - </form> - - - <?php - } - - // Pterodactyl Config - if (isset($_GET['step']) && $_GET['step'] == 7) { - echo cardStart($title = "Pterodactyl Configuration", $subtitle = "Lets get some info about your Pterodactyl Installation!"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkPtero"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - <div class="row"> - <div class="col-md-12"> - <div class="form-group"> - <div class="flex flex-col mb-3"> - - <label for="url">Pterodactyl URL</label> - <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="key">Application API Key</label> - <input id="key" name="key" type="text" required value="" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/admin/api] <br/> The key needs all - Read & Write permissions! </span> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="clientkey">Admin User Client API Key</label> - <input id="clientkey" name="clientkey" type="text" required value="" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">[Found at: ptero.example.com/account/api] <br/> Your Account - needs to be an Admin!</span> - </div> - </div> - </div> - </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkPtero">Submit - </button> - </div> - </form> - - - <?php - } - - // Admin Creation Form - if (isset($_GET['step']) && $_GET['step'] == 8) { - echo cardStart($title = "First Admin Creation", $subtitle = "Lets create the first admin user!"); ?> - - <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="createUser"> - - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; - } ?> - - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pteroID">Pterodactyl User ID </label> - <input id="pteroID" name="pteroID" type="text" required value="1" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> - </div> - </div> - - <div class="form-group"> - <div class="flex flex-col mb-3"> - <label for="pass">Password</label> - <input id="pass" name="pass" type="password" required value="" minlength="8" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> - </div> - </div> - <div class="form-group"> - <div class="flex flex-col"> - <label for="repass">Confirm Password</label> - <input id="repass" name="repass" type="password" required value="" minlength="8" - class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> - </div> - </div> - - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="createUser">Submit - </button> - </div> - </form> - - - <?php - } - - // Install Finished - if (isset($_GET['step']) && $_GET['step'] == 9) { - $lockfile = fopen('../../installer.lock', 'w') or exit('Unable to open file!'); - fwrite($lockfile, 'locked'); - fclose($lockfile); - - echo cardStart($title = "Installation Complete!", $subtitle = "You may navigate to your Dashboard now and log in!"); - ?> - - <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> - <button - class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500"> - Lets Go! - </button> - </a> - - <?php - } - ?> -</body> -</html> +$viewNames = [ + 1 => 'mandatory-checks', + 2 => 'timezone-configuration', + 3 => 'database-configuration', + 4 => 'database-migration', + 5 => 'redis-configuration', + 6 => 'dashboard-configuration', + 7 => 'email-configuration', + 8 => 'pterodactyl-configuration', + 9 => 'admin-creation', + 10 => 'installation-complete', +]; + +$step = isset($_GET['step']) ? $_GET['step'] : 1; +$viewName = $viewNames[$step]; // Get the appropriate view name + +// Load the layout and the specific view file +include './views/layout-top.php'; +include "./views/{$viewName}.php"; +include './views/layout-bottom.php'; + +?> \ No newline at end of file diff --git a/public/installer/views/admin-creation.php b/public/installer/views/admin-creation.php index e69de29bb..b2eb9fa4f 100644 --- a/public/installer/views/admin-creation.php +++ b/public/installer/views/admin-creation.php @@ -0,0 +1,48 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "First Admin Creation", + $subtitle = "Lets create the first admin user!" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="createUser"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pteroID">Pterodactyl User ID </label> + <input id="pteroID" name="pteroID" type="text" required value="1" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">Found in the users-list on your pterodactyl dashboard</span> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Password</label> + <input id="pass" name="pass" type="password" required value="" minlength="8" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400">This will be your new pterodactyl password aswell!</span> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="repass">Confirm Password</label> + <input id="repass" name="repass" type="password" required value="" minlength="8" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="createUser">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php index e69de29bb..642b0ddf5 100644 --- a/public/installer/views/dashboard-configuration.php +++ b/public/installer/views/dashboard-configuration.php @@ -0,0 +1,42 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Dashboard Configuration" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkGeneral"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="database">Dashboard URL</label> + <input id="url" name="url" type="text" required + value="<?php echo 'https://' . $_SERVER['SERVER_NAME']; ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="name">Dashboard Name</label> + <input id="name" name="name" type="text" required value="CtrlPanel" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkGeneral">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/database-configuration.php b/public/installer/views/database-configuration.php index e69de29bb..5aa96dd20 100644 --- a/public/installer/views/database-configuration.php +++ b/public/installer/views/database-configuration.php @@ -0,0 +1,75 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Database Configuration" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkDB"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasedriver">Database Driver</label> + <input x-model="databasedriver" id="databasedriver" name="databasedriver" type="text" required + value="mysql" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databasehost">Database Host</label> + <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required + value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseport">Database Port</label> + <input x-model="databaseport" id="databaseport" name="databaseport" type="number" required + value="3306" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuser">Database User</label> + <input x-model="databaseuser" id="databaseuser" name="databaseuser" type="text" required + value="ctrlpaneluser" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="databaseuserpass">Database User Password</label> + <input x-model="databaseuserpass" id="databaseuserpass" name="databaseuserpass" type="text" + required + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col"> + <label for="database">Database</label> + <input x-model="database" id="database" name="database" type="text" required value="ctrlpanel" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkDB">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index e69de29bb..cf12ba40c 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -0,0 +1,23 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Database Migration and Encryption Key Generation", + $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="feedDB"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="feedDB">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/email-configuration.php b/public/installer/views/email-configuration.php index e69de29bb..a7545fdae 100644 --- a/public/installer/views/email-configuration.php +++ b/public/installer/views/email-configuration.php @@ -0,0 +1,80 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "E-Mail Configuration", + $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkSMTP"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="method">E-Mail Protocol</label> + <select id="method" name="method" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="smtp" selected>SMTP</option> + </select> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="host">Your Mailer-Host</label> + <input id="host" name="host" type="text" required value="smtp.google.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="port">Your Mail Port</label> + <input id="port" name="port" type="number" required value="567" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="user">Your Mail User</label> + <input id="user" name="user" type="text" required value="info@mydomain.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="pass">Your Mail-User Password</label> + <input id="pass" name="pass" type="password" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + + <div class="form-group"> + <div class="flex flex-col"> + <label for="encryption">Your Mail encryption method</label> + <select id="encryption" name="encryption" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="tls" selected>TLS</option> + <option value="ssl">SSL</option> + <option value="null">None</option> + </select> + </div> + </div> + </div> + </div> + + <div class="flex w-full justify-around mt-4 gap-8 px-8"> + <button type="submit" + class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkSMTP">Submit + </button> + + <a href="?step=7" class="w-full"> + <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600"> + Skip For Now + </button> + </a> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/installation-complete.php b/public/installer/views/installation-complete.php index e69de29bb..c9e04d23d 100644 --- a/public/installer/views/installation-complete.php +++ b/public/installer/views/installation-complete.php @@ -0,0 +1,22 @@ + +<!-- top layout here --> + +<?php +$lockfile = fopen('../../installer.lock', 'w') or exit('Unable to open file!'); +fwrite($lockfile, 'the installation is locked, delete this file to unlock it'); +fclose($lockfile); + +echo cardStart( + $title = "Installation Complete!", + $subtitle = "You may navigate to your Dashboard now and log in!" +); +?> + +<a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> + <button + class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500"> + Lets Go! + </button> +</a> + +<!-- bottom layout here --> diff --git a/public/installer/views/layout-bottom.php b/public/installer/views/layout-bottom.php new file mode 100644 index 000000000..35bfcba04 --- /dev/null +++ b/public/installer/views/layout-bottom.php @@ -0,0 +1,7 @@ + +<!-- top layout here --> + +<!-- any middle view here --> + + </body> +</html> diff --git a/public/installer/views/layout-top.php b/public/installer/views/layout-top.php new file mode 100644 index 000000000..3d2b89382 --- /dev/null +++ b/public/installer/views/layout-top.php @@ -0,0 +1,59 @@ +<html lang="en"> +<head> + <title>CtrlPanel.gg installer Script</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link href="/installer/styles.css" rel="stylesheet"> + <style> + body { + color-scheme: dark; + } + + .check { + display: flex; + gap: 5px; + align-items: center; + margin-bottom: 5px; + } + + .check::before { + width: 20px; + height: 20px; + display: block; + } + + .ok { + color: lightgreen; + } + + /* Green Checkmark */ + .ok::before { + content: url("data:image/svg+xml,%3Csvg fill='none' stroke='lightgreen' stroke-width='1.5' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); + } + + .not-ok { + color: lightcoral; + } + + /* Red Cross */ + .not-ok::before { + content: url("data:image/svg+xml,%3Csvg fill='none' stroke='lightcoral' stroke-width='1.5' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); + } + </style> +</head> + +<body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> + <?php + function cardStart($title, $subtitle = null): string + { + return " + <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> + <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> + <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> + <h2 class='text-xl text-center mb-2'>$title</h2>" + . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); + } + ?> + +<!-- any middle view here --> + +<!-- bottom layout here --> diff --git a/public/installer/views/mandatory-checks.php b/public/installer/views/mandatory-checks.php index e69de29bb..039f378c4 100644 --- a/public/installer/views/mandatory-checks.php +++ b/public/installer/views/mandatory-checks.php @@ -0,0 +1,54 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Mandatory Checks before Installation", + $subtitle = "This installer will lead you through the most crucial Steps of CtrlPanel.gg's setup" +); ?> + +<ul class="list-none mb-2"> + + <li class="<?php echo checkWriteable() ? 'ok' : 'not-ok'; ?> check">Write-permissions on .env-file</li> + + <li class="<?php echo checkPhpVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + php version: <?php echo phpversion(); ?> (minimum required <?php echo $requirements['minPhp']; ?>) + </li> + + <li class="<?php echo count(checkExtensions()) == 0 ? 'ok' : 'not-ok'; ?> check"> + Missing php-extentions: + <?php echo count(checkExtensions()) == 0 ? 'none' : ''; + foreach (checkExtensions() as $ext) { + echo $ext . ', '; + } + echo count(checkExtensions()) === 0 ? '' : '(Proceed anyway)'; ?> + </li> + + <li class="<?php echo getGitVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Git version: + <?php echo getGitVersion(); ?> + </li> + + <li class="<?php echo getTarVersion() === 'OK' ? 'ok' : 'not-ok'; ?> check"> + Tar version: + <?php echo getTarVersion(); ?> + </li> + + <li> + <p class="text-neutral-400 mb-1"> + <br> + <span style="color: #eab308;">Important:</span> + CtrlPanel.gg requires a MySQL-Database, Redis-Server, and Pterodactyl-Panel to work.<br> + Please make sure you have these installed and running before you continue. + </p> + </li> + +</ul> + +<a href="?step=2" class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> + Lets go! + </button> +</a> + +<!-- bottom layout here --> diff --git a/public/installer/views/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php index e69de29bb..47ca4acda 100644 --- a/public/installer/views/pterodactyl-configuration.php +++ b/public/installer/views/pterodactyl-configuration.php @@ -0,0 +1,52 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Pterodactyl Configuration", + $subtitle = "Lets get some info about your Pterodactyl Installation!" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkPtero"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="url">Pterodactyl URL</label> + <input id="url" name="url" type="text" required placeholder="https://ptero.example.com" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="key">Application API Key</label> + <input id="key" name="key" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400"> + [Found at: ptero.example.com/admin/api] <br/> + The key needs all Read & Write permissions! </span> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col"> + <label for="clientkey">Admin User Client API Key</label> + <input id="clientkey" name="clientkey" type="text" required value="" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <span class="text-neutral-400"> + [Found at: ptero.example.com/account/api] <br/> + Your Account needs to be an Admin!</span> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="checkPtero">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index e69de29bb..76bc67380 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -0,0 +1,51 @@ + +<!-- top layout here --> + +<?php echo cardStart( + $title = "Redis Configuration" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="redisSetup"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="redishost">Redis Host</label> + <input x-model="redishost" id="redishost" name="redishost" type="text" required + value="<?php echo(determineIfRunningInDocker() ? 'redis' : '127.0.0.1') ?>" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="redisport">Redis Port</label> + <input x-model="redisport" id="redisport" name="redisport" type="number" required + value="6379" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="redispassword">Redis Password (optionally, only if configured)</label> + <input x-model="redispassword" id="redispassword" name="redispassword" type="text" + placeholder="usually can be left blank" + class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="redisSetup">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index e69de29bb..0d2228773 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -0,0 +1,42 @@ +<!-- top layout here --> + +<?php echo cardStart( + $title = "Timezone Configuration" +); ?> + +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="timezoneConfig"> + + <?php if (isset($_GET['message'])) { + echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + } ?> + + <div class="row"> + <div class="col-md-12"> + <div class="form-group"> + <div class="flex flex-col mb-3"> + <label for="timezone">Timezone</label> + <select id="timezone" name="timezone" required + class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <?php + foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { + if ($timezoneIdentifier === 'UTC') { + continue; + } + + echo '<option value="' . $timezoneIdentifier . '">' . $timezoneIdentifier . '</option>'; + } ?> + </select> + </div> + </div> + </div> + </div> + + <div class="w-full flex justify-center"> + <button + class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" + name="timezoneConfig">Submit + </button> + </div> +</form> + +<!-- bottom layout here --> \ No newline at end of file From cdb955bb147108d59f104ffc262ca1840bf5b8e5 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 11:17:55 +0200 Subject: [PATCH 430/514] fix: changed database oops reverting back change --- config/database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/database.php b/config/database.php index 1917bc6a7..dc722b5fd 100644 --- a/config/database.php +++ b/config/database.php @@ -119,7 +119,7 @@ 'redis' => [ - 'client' => env('REDIS_CLIENT', 'predis'), + 'client' => env('REDIS_CLIENT', 'phpredis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), From db52d1e895e02775005c78433b303bea1f81e4f4 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Thu, 13 Jun 2024 15:14:28 +0200 Subject: [PATCH 431/514] refactor: too many things, refactored the form and functions and added better step and error handling --- public/installer/forms.php | 384 ------------------ public/installer/functions.php | 280 ------------- public/installer/index.php | 51 ++- public/installer/src/forms/admin.php | 96 +++++ public/installer/src/forms/dashboard.php | 29 ++ public/installer/src/forms/database.php | 62 +++ public/installer/src/forms/pterodactyl.php | 80 ++++ public/installer/src/forms/redis.php | 32 ++ public/installer/src/forms/smtp.php | 71 ++++ public/installer/src/forms/timezone.php | 13 + public/installer/src/functions/database.php | 0 .../installer/src/functions/environment.php | 132 ++++++ public/installer/src/functions/installer.php | 16 + public/installer/src/functions/logging.php | 45 ++ public/installer/src/functions/shell.php | 41 ++ public/installer/src/functions/utils.php | 41 ++ public/installer/views/admin-creation.php | 6 +- .../views/dashboard-configuration.php | 6 +- .../views/database-configuration.php | 6 +- public/installer/views/database-migration.php | 6 +- .../installer/views/email-configuration.php | 6 +- .../views/pterodactyl-configuration.php | 6 +- .../installer/views/redis-configuration.php | 6 +- .../views/timezone-configuration.php | 6 +- 24 files changed, 721 insertions(+), 700 deletions(-) delete mode 100644 public/installer/forms.php delete mode 100644 public/installer/functions.php delete mode 100644 public/installer/src/functions/database.php create mode 100644 public/installer/src/functions/installer.php diff --git a/public/installer/forms.php b/public/installer/forms.php deleted file mode 100644 index 6c8795a13..000000000 --- a/public/installer/forms.php +++ /dev/null @@ -1,384 +0,0 @@ -<?php -ini_set('display_errors', 1); -ini_set('display_startup_errors', 1); -error_reporting(E_ALL); - -use PHPMailer\PHPMailer\Exception; -use PHPMailer\PHPMailer\PHPMailer; -use Predis\Client; - -require './src/phpmailer/Exception.php'; -require './src/phpmailer/PHPMailer.php'; -require './src/phpmailer/SMTP.php'; - -if (isset($_POST['timezoneConfig'])) { - wh_log('Setting up Timezone', 'debug'); - $timezone = $_POST['timezone']; - - setenv('APP_TIMEZONE', $timezone); - - wh_log('Timezone set: ' . $timezone, 'debug'); - header('LOCATION: index.php?step=3'); -} - -mysqli_report(MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ALL); - -if (isset($_POST['checkDB'])) { - $values = [ - //SETTINGS::VALUE => REQUEST-VALUE (coming from the html-form) - 'DB_HOST' => 'databasehost', - 'DB_DATABASE' => 'database', - 'DB_USERNAME' => 'databaseuser', - 'DB_PASSWORD' => 'databaseuserpass', - 'DB_PORT' => 'databaseport', - 'DB_CONNECTION' => 'databasedriver', - ]; - - wh_log('Trying to connect to the Database', 'debug'); - - try { - $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); - } catch (mysqli_sql_exception $e) { - wh_log($e->getMessage(), 'error'); - header('LOCATION: index.php?step=3&message=' . $e->getMessage()); - exit(); - } - - - foreach ($values as $key => $value) { - $param = $_POST[$value]; - // if ($key == "DB_PASSWORD") { - // $param = '"' . $_POST[$value] . '"'; - // } - setenv($key, $param); - } - - wh_log('Start APP_KEY generation', 'debug'); - - try { - if (!str_contains(getenv('APP_KEY'), 'base64')) { - $logs = run_console('php artisan key:generate --force'); - wh_log($logs, 'debug'); - - wh_log('Created APP_KEY successful', 'debug'); - } else { - wh_log('Key already exists. Skipping', 'debug'); - } - } catch (Throwable $th) { - wh_log('Creating APP_KEY failed', 'error'); - header("LOCATION: index.php?step=3&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); - exit(); - } - - wh_log('Database connection successful', 'debug'); - header('LOCATION: index.php?step=3.5'); -} - -if (isset($_POST['feedDB'])) { - wh_log('Feeding the Database', 'debug'); - $logs = ''; - - try { - //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); - //$logs .= run_console('composer install --no-dev --optimize-autoloader'); - $logs .= run_console('php artisan storage:link'); - $logs .= run_console('php artisan migrate --seed --force'); - $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); - $logs .= run_console('php artisan db:seed --class=PermissionsSeeder --force'); - - wh_log($logs, 'debug'); - - wh_log('Feeding the Database successful', 'debug'); - header('LOCATION: index.php?step=4'); - } catch (Throwable $th) { - wh_log('Feeding the Database failed', 'error'); - header("LOCATION: index.php?step=3.5&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); - } -} - -if (isset($_POST['redisSetup'])) { - wh_log('Setting up Redis', 'debug'); - $redisHost = $_POST['redishost']; - $redisPort = $_POST['redisport']; - $redisPassword = $_POST['redispassword']; - - $redisClient = new Client([ - 'host' => $redisHost, - 'port' => $redisPort, - 'password' => $redisPassword, - 'timeout' => 1.0, - ]); - - try { - $redisClient->ping(); - - setenv('MEMCACHED_HOST', $redisHost); - setenv('REDIS_HOST', $redisHost); - setenv('REDIS_PORT', $redisPort); - setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); - - wh_log('Redis connection successful. Settings updated.', 'debug'); - header('LOCATION: index.php?step=5'); - } catch (Throwable $th) { - wh_log('Redis connection failed. Settings updated.', 'debug'); - header("LOCATION: index.php?step=4&message=Please check your credentials!<br>" . $th->getMessage()); - } -} - -if (isset($_POST['checkGeneral'])) { - wh_log('setting app settings', 'debug'); - $appname = '"' . $_POST['name'] . '"'; - $appurl = $_POST['url']; - - $parsedUrl = parse_url($appurl); - - if (!isset($parsedUrl['scheme'])) { - header('LOCATION: index.php?step=5&message=Please set an URL Scheme like "https://"!'); - exit(); - } - - if (!isset($parsedUrl['host'])) { - header('LOCATION: index.php?step=5&message=Please set an valid URL host like "https://ctrlpanel.example.com"!'); - exit(); - } - - $appurl = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; - - setenv('APP_NAME', $appname); - setenv('APP_URL', $appurl); - - wh_log('App settings set', 'debug'); - header('LOCATION: index.php?step=6'); -} - -if (isset($_POST['checkSMTP'])) { - wh_log('Checking SMTP Settings', 'debug'); - try { - $mail = new PHPMailer(true); - - //Server settings - // Send using SMTP - $mail->isSMTP(); - $mail->Host = $_POST['host']; - // Enable SMTP authentication - $mail->SMTPAuth = true; - $mail->Username = $_POST['user']; - $mail->Password = $_POST['pass']; - $mail->SMTPSecure = $_POST['encryption']; - $mail->Port = (int) $_POST['port']; - - // Test E-mail metadata - $mail->setFrom($_POST['user'], $_POST['user']); - $mail->addAddress($_POST['user'], $_POST['user']); - - // Content - // Set email format to HTML - $mail->isHTML(true); - $mail->Subject = 'It Worked! - Test E-Mail from Ctrlpanel.gg'; - $mail->Body = 'Your E-Mail Settings are correct!'; - - $mail->send(); - } catch (Exception $e) { - wh_log($mail->ErrorInfo, 'error'); - header('LOCATION: index.php?step=6&message=Something went wrong while sending test E-Mail!<br>' . $mail->ErrorInfo); - exit(); - } - - wh_log('SMTP Settings are correct', 'debug'); - wh_log('Updating Database', 'debug'); - $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); - if ($db->connect_error) { - wh_log($db->connect_error, 'error'); - header('LOCATION: index.php?step=6&message=Could not connect to the Database: '); - exit(); - } - $values = [ - 'mail_mailer' => $_POST['method'], - 'mail_host' => $_POST['host'], - 'mail_port' => $_POST['port'], - 'mail_username' => $_POST['user'], - 'mail_password' => $_POST['pass'], - 'mail_encryption' => $_POST['encryption'], - 'mail_from_address' => $_POST['user'], - ]; - - foreach ($values as $key => $value) { - run_console("php artisan settings:set 'MailSettings' '$key' '$value'"); - } - - wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=7'); -} - -if (isset($_POST['checkPtero'])) { - wh_log('Checking Pterodactyl Settings', 'debug'); - - $url = $_POST['url']; - $key = $_POST['key']; - $clientkey = $_POST['clientkey']; - - $parsedUrl = parse_url($url); - - if (!isset($parsedUrl['scheme'])) { - header('LOCATION: index.php?step=7&message=Please set an URL Scheme like "https://"!'); - exit(); - } - - if (!isset($parsedUrl['host'])) { - header('LOCATION: index.php?step=7&message=Please set an valid URL host like "https://panel.example.com"!'); - exit(); - } - - $url = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; - - $callpteroURL = $url . '/api/client/account'; - $call = curl_init(); - - curl_setopt($call, CURLOPT_URL, $callpteroURL); - curl_setopt($call, CURLOPT_RETURNTRANSFER, true); - curl_setopt($call, CURLOPT_HTTPHEADER, [ - 'Accept: Application/vnd.pterodactyl.v1+json', - 'Content-Type: application/json', - 'Authorization: Bearer ' . $clientkey, - ]); - $callresponse = curl_exec($call); - $callresult = json_decode($callresponse, true); - curl_close($call); - - $pteroURL = $url . '/api/application/users'; - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $pteroURL); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Accept: Application/vnd.pterodactyl.v1+json', - 'Content-Type: application/json', - 'Authorization: Bearer ' . $key, - ]); - $response = curl_exec($ch); - $result = json_decode($response, true); - curl_close($ch); - - if (!is_array($result)) { - wh_log('No array in response found', 'error'); - header('LOCATION: index.php?step=7&message=An unknown Error occured, please try again!'); - } - - if (array_key_exists('errors', $result) && $result['errors'][0]['detail'] === 'This action is unauthorized.') { - wh_log('API CALL ERROR: ' . $result['errors'][0]['code'], 'error'); - header('LOCATION: index.php?step=7&message=Couldn\'t connect to Pterodactyl. Make sure your Application API key has all read and write permissions!'); - exit(); - } - - if (array_key_exists('errors', $callresult) && $callresult['errors'][0]['detail'] === 'Unauthenticated.') { - wh_log('API CALL ERROR: ' . $callresult['errors'][0]['code'], 'error'); - header('LOCATION: index.php?step=7&message=Your ClientAPI Key is wrong or the account is not an admin!'); - exit(); - } - - try { - run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'"); - run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); - run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); - wh_log('Database updated', 'debug'); - header('LOCATION: index.php?step=8'); - } catch (Throwable $th) { - wh_log("Setting Pterodactyl information failed.", 'error'); - header("LOCATION: index.php?step=7&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); - exit(); - } -} - -if (isset($_POST['createUser'])) { - wh_log('Getting Pterodactyl User', 'debug'); - - try { - $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); - } catch (Throwable $th) { - wh_log($th->getMessage(), 'error'); - header('LOCATION: index.php?step=8&message=Could not connect to the Database'); - exit(); - } - - $pteroID = $_POST['pteroID']; - $pass = $_POST['pass']; - $repass = $_POST['repass']; - - try { - $panelUrl = run_console("php artisan settings:get 'PterodactylSettings' 'panel_url' --sameline"); - $adminToken = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); - } catch (Throwable $th) { - wh_log("Getting Pterodactyl information failed.", 'error'); - header("LOCATION: index.php?step=7&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); - exit(); - } - - $panelApiUrl = $panelUrl . '/api/application/users/' . $pteroID; - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $panelApiUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Accept: application/json', - 'Content-Type: application/json', - 'Authorization: Bearer ' . $adminToken, - ]); - $response = curl_exec($ch); - $result = json_decode($response, true); - curl_close($ch); - - if ($pass !== $repass) { - header('LOCATION: index.php?step=8&message=The Passwords did not match!'); - exit(); - } - - if (array_key_exists('errors', $result)) { - header('LOCATION: index.php?step=8&message=Could not find the user with pterodactyl ID ' . $pteroID); - exit(); - } - - $mail = $result['attributes']['email']; - $name = $result['attributes']['username']; - $pass = password_hash($pass, PASSWORD_DEFAULT); - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $panelApiUrl); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Accept: application/json', - 'Content-Type: application/json', - 'Authorization: Bearer ' . $adminToken, - ]); - curl_setopt($ch, CURLOPT_POSTFIELDS, [ - 'email' => $mail, - 'username' => $name, - 'first_name' => $name, - 'last_name' => $name, - 'password' => $pass, - ]); - $response = curl_exec($ch); - $result = json_decode($response, true); - curl_close($ch); - - $random = generateRandomString(); - - $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; - $query2 = "INSERT INTO `" . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\\\Models\\\User', '1')"; - try { - $db->query($query1); - $db->query($query2); - - wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID); - header('LOCATION: index.php?step=9'); - } catch (Throwable $th) { - wh_log($th->getMessage(), 'error'); - if (str_contains($th->getMessage(), 'Duplicate entry')) { - header('LOCATION: index.php?step=8&message=User already exists in CtrlPanel\'s Database.'); - } else { - header('LOCATION: index.php?step=8&message=Something went wrong when communicating with the Database.'); - } - exit(); - } -} diff --git a/public/installer/functions.php b/public/installer/functions.php deleted file mode 100644 index e450e8cc1..000000000 --- a/public/installer/functions.php +++ /dev/null @@ -1,280 +0,0 @@ -<?php -require '../../vendor/autoload.php'; -require 'dotenv.php'; - -use DevCoder\DotEnv; -use Illuminate\Encryption\Encrypter; -use Illuminate\Support\Str; -use Monolog\Formatter\LineFormatter; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; - -if (!file_exists('../../.env')) { - echo run_console('cp .env.example .env'); -} - -(new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); - -$required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl', 'redis']; - -$requirements = [ - 'minPhp' => '8.1', - 'maxPhp' => '8.4', // This version is not supported - 'mysql' => '5.7.22', -]; - -/** - * Check if the minimum PHP version is present - * @return string 'OK' on success and 'not OK' on failure. - */ -function checkPhpVersion(): string -{ - global $requirements; - - wh_log('php version: ' . phpversion(), 'debug'); - if (version_compare(phpversion(), $requirements['minPhp'], '>=') && version_compare(phpversion(), $requirements['maxPhp'], '<=')) { - return 'OK'; - } - - return 'not OK'; -} - -/** - * Check if the environment file is writable - * @return bool Returns true on writable and false on not writable. - */ -function checkWriteable(): bool -{ - return is_writable('../../.env'); -} - -/** - * Check if zip is installed using a shell command - * @return string 'OK' on success and 'not OK' on failure. - */ -function getZipVersion(): string -{ - wh_log('attempting to get zip version', 'debug'); - $output = shell_exec('zip -v') ?? ''; - preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); - - $versionoutput = $version[0] ?? 0; - wh_log('zip version: ' . $versionoutput, 'debug'); - - return $versionoutput != 0 ? 'OK' : 'not OK'; -} - -/** - * Check if git is installed using a shell command - * @return string 'OK' on success and 'not OK' on failure. - */ -function getGitVersion(): string -{ - wh_log('attempting to get git version', 'debug'); - $output = shell_exec('git --version') ?? ''; - preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); - - $versionoutput = $version[0] ?? 0; - wh_log('git version: ' . $versionoutput, 'debug'); - - return $versionoutput != 0 ? 'OK' : 'not OK'; -} - -/** - * Check if tar is installed using a shell command - * @return string 'OK' on success and 'not OK' on failure. - */ -function getTarVersion(): string -{ - wh_log('attempting to get tar version', 'debug'); - $output = shell_exec('tar --version') ?? ''; - preg_match('@[0-9]+\.[0-9]+@', $output, $version); - - $versionoutput = $version[0] ?? 0; - wh_log('tar version: ' . $versionoutput, 'debug'); - - return $versionoutput != 0 ? 'OK' : 'not OK'; -} - -/** - * Check all extensions to see if they have loaded or not - * @return array Returns an array of extensions that failed to load. - */ -function checkExtensions(): array -{ - global $required_extensions; - - wh_log('checking extensions', 'debug'); - - $not_ok = []; - $extentions = get_loaded_extensions(); - - foreach ($required_extensions as $ext) { - if (!preg_grep('/^(?=.*' . $ext . ').*$/', $extentions)) { - array_push($not_ok, $ext); - } - } - - wh_log('loaded extensions:', 'debug', $extentions); - wh_log('failed extensions:', 'debug', $not_ok); - return $not_ok; -} - -function removeQuotes($string) -{ - return str_replace('"', "", $string); -} - -/** - * Sets the environment variable into the env file - * @param string $envKey The environment key to set or modify - * @param string $envValue The environment variable to set - * @return bool true on success or false on failure. - */ -function setenv($envKey, $envValue) -{ - $envFile = dirname(__FILE__, 3) . '/.env'; - $str = file_get_contents($envFile); - - $str .= "\n"; // In case the searched variable is in the last line without \n - $keyPosition = strpos($str, "{$envKey}="); - $endOfLinePosition = strpos($str, PHP_EOL, $keyPosition); - $oldLine = substr($str, $keyPosition, $endOfLinePosition - $keyPosition); - $str = str_replace($oldLine, "{$envKey}={$envValue}", $str); - $str = substr($str, 0, -1); - - $fp = fopen($envFile, 'w'); - fwrite($fp, $str); - fclose($fp); -} - -/** - * Encrypt the given value - * @param mixed $value The variable to be encrypted - * @param bool $serialize If the encryption should be serialized - * @return string Returns the encrypted variable. - */ -function encryptSettingsValue(mixed $value, $serialize = true): string -{ - $appKey = getenv('APP_KEY'); - $appKey = base64_decode(Str::after($appKey, 'base64:')); - $encrypter = new Encrypter($appKey, 'AES-256-CBC'); - $encryptedKey = $encrypter->encrypt($value, $serialize); - - return $encryptedKey; -} - -/** - * Decrypt the given value - * @param mixed $payload The payload to be decrypted - * @param bool $unserialize If the encryption should be unserialized - * @return mixed Returns the decrypted variable on success, throws otherwise. - */ - -function decryptSettingsValue(mixed $payload, $unserialize = true) -{ - $appKey = getenv('APP_KEY'); - $appKey = base64_decode(Str::after($appKey, 'base64:')); - $encrypter = new Encrypter($appKey, 'AES-256-CBC'); - $decryptedKey = $encrypter->decrypt($payload, $unserialize); - - return $decryptedKey; -} - -/** - * Run a shell command - * @param string $command The command string to run - * @param array|null $descriptors [optional]<p> - * An indexed array where the key represents the descriptor number and the value represents how PHP will pass that descriptor to the child process. 0 is stdin, 1 is stdout, while 2 is stderr. - * Default descriptors when null are 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] - * </p> - * @param string|null $cwd [optional] <p> - * The initial working dir for the command. This must be an - * absolute directory path, or null - * if you want to use the default value (the working dir of the current - * PHP process) - * </p> - * @param array|null $options [optional] <p> - * Allows you to specify additional options. - * @link https://www.php.net/manual/en/function.proc-open.php proc_open - * </p> - * @return false|string|null Returns the result from the command. - */ -function run_console(string $command, array $descriptors = null, string $cwd = null, array $options = null) -{ - wh_log('running command: ' . $command, 'debug'); - - $path = dirname(__FILE__, 3); - $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); - $output = stream_get_contents($pipes[1]); - $exit_code = proc_close($handle); - - if ($exit_code > 0) { - wh_log('command result: ' . $output, 'error'); - throw new Exception("There was an error after running command `$command`", $exit_code); - return $output; - } else { - return $output; - } -} - -/** - * Log to the default laravel.log file - * @param string $message The message to log - * @param string $level The log level to use (debug, info, warning, error, critical) - * @param array $context [optional] The context to log extra information - * @return void - */ -function wh_log(string $message, string $level = 'info', array $context = []): void -{ - $formatter = new LineFormatter(null, null, true, true); - $stream = new StreamHandler(dirname(__FILE__, 3) . '/storage/logs/installer.log', Logger::DEBUG); - $stream->setFormatter($formatter); - - $log = new Logger('CtrlPanel'); - $log->pushHandler($stream); - - switch (strtolower($level)) { - case 'debug': // Only log debug messages if APP_DEBUG is true - if (getenv('APP_DEBUG') === false) return; - $log->debug($message, $context); - break; - case 'info': - $log->info($message, $context); - break; - case 'warning': - $log->warning($message, $context); - break; - case 'error': - $log->error($message, $context); - break; - case 'critical': - $log->critical($message, $context); - break; - } - // Prevent memory leaks by resetting the logger - $log->reset(); -} - -/** - * Generate a random string - * @param int $length The length of the random string - * @return string The randomly generated string. - */ -function generateRandomString(int $length = 8): string -{ - $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $charactersLength = strlen($characters); - $randomString = ''; - for ($i = 0; $i < $length; $i++) { - $randomString .= $characters[rand(0, $charactersLength - 1)]; - } - - return $randomString; -} - -function determineIfRunningInDocker(): bool -{ - return file_exists('/.dockerenv'); -} diff --git a/public/installer/index.php b/public/installer/index.php index 24ee47bbd..4feed33b6 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -1,28 +1,43 @@ <?php +ini_set('display_errors', 1); +ini_set('display_startup_errors', 1); +error_reporting(E_ALL); + +session_start(); + +use DevCoder\DotEnv; +// use Illuminate\Encryption\Encrypter; +// use Illuminate\Support\Str; + +require '../../vendor/autoload.php'; +require 'dotenv.php'; // Include the function files +require_once './src/functions/installer.php'; // very important require_once './src/functions/environment.php'; -require_once './src/functions/database.php'; require_once './src/functions/shell.php'; require_once './src/functions/logging.php'; require_once './src/functions/utils.php'; // Include the form files -require_once './src/forms/timezone.php'; -require_once './src/forms/database.php'; -require_once './src/forms/redis.php'; -require_once './src/forms/dashboard.php'; -require_once './src/forms/smtp.php'; -require_once './src/forms/pterodactyl.php'; -require_once './src/forms/admin.php'; - -require_once './functions.php'; -require_once './forms.php'; +include './src/forms/timezone.php'; +include './src/forms/database.php'; +include './src/forms/redis.php'; +include './src/forms/dashboard.php'; +include './src/forms/smtp.php'; +include './src/forms/pterodactyl.php'; +include './src/forms/admin.php'; if (file_exists('../../install.lock')) { exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } +if (!file_exists('../../.env')) { + echo run_console('cp .env.example .env'); +} + +(new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); + $viewNames = [ 1 => 'mandatory-checks', 2 => 'timezone-configuration', @@ -36,7 +51,16 @@ 10 => 'installation-complete', ]; -$step = isset($_GET['step']) ? $_GET['step'] : 1; +// Prioritize $_GET['step'], then session, then default to 1 +$step = isset($_GET['step']) + ? (int)$_GET['step'] // Convert to integer for safety + : (isset($_SESSION['installation_step']) + ? $_SESSION['installation_step'] + : 1); + +// Update session with the current step +$_SESSION['installation_step'] = $step; + $viewName = $viewNames[$step]; // Get the appropriate view name // Load the layout and the specific view file @@ -44,4 +68,7 @@ include "./views/{$viewName}.php"; include './views/layout-bottom.php'; +// setting / reseting the error message +$_SESSION['error-message'] = null; + ?> \ No newline at end of file diff --git a/public/installer/src/forms/admin.php b/public/installer/src/forms/admin.php index e69de29bb..9d0df6229 100644 --- a/public/installer/src/forms/admin.php +++ b/public/installer/src/forms/admin.php @@ -0,0 +1,96 @@ +<?php + +if (isset($_POST['createUser'])) { + wh_log('Getting Pterodactyl User', 'debug'); + + try { + $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); + } catch (Throwable $th) { + wh_log($th->getMessage(), 'error'); + send_error_message("Could not connect to the Database"); + exit(); + } + + $pteroID = $_POST['pteroID']; + $pass = $_POST['pass']; + $repass = $_POST['repass']; + + try { + $panelUrl = run_console("php artisan settings:get 'PterodactylSettings' 'panel_url' --sameline"); + $adminToken = run_console("php artisan settings:get 'PterodactylSettings' 'admin_token' --sameline"); + } catch (Throwable $th) { + wh_log("Getting Pterodactyl information failed.", 'error'); + send_error_message($th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + exit(); + } + + $panelApiUrl = $panelUrl . '/api/application/users/' . $pteroID; + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $panelApiUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Accept: application/json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $adminToken, + ]); + $response = curl_exec($ch); + $result = json_decode($response, true); + curl_close($ch); + + if ($pass !== $repass) { + send_error_message("The Passwords did not match!"); + exit(); + } + + if (array_key_exists('errors', $result)) { + send_error_message("Could not find the user with pterodactyl ID" . $pteroID); + exit(); + } + + $mail = $result['attributes']['email']; + $name = $result['attributes']['username']; + $pass = password_hash($pass, PASSWORD_DEFAULT); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $panelApiUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Accept: application/json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $adminToken, + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, [ + 'email' => $mail, + 'username' => $name, + 'first_name' => $name, + 'last_name' => $name, + 'password' => $pass, + ]); + $response = curl_exec($ch); + $result = json_decode($response, true); + curl_close($ch); + + $random = substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 8); // random referal + + $query1 = 'INSERT INTO `' . getenv('DB_DATABASE') . "`.`users` (`name`, `role`, `credits`, `server_limit`, `pterodactyl_id`, `email`, `password`, `created_at`, `referral_code`) VALUES ('$name', 'admin', '250', '1', '$pteroID', '$mail', '$pass', CURRENT_TIMESTAMP, '$random')"; + $query2 = "INSERT INTO `" . getenv('DB_DATABASE') . "`.`model_has_roles` (`role_id`, `model_type`, `model_id`) VALUES ('1', 'App\\\Models\\\User', '1')"; + try { + $db->query($query1); + $db->query($query2); + + wh_log('Created user with Email ' . $mail . ' and pterodactyl ID ' . $pteroID); + next_step(); + } catch (Throwable $th) { + wh_log($th->getMessage(), 'error'); + if (str_contains($th->getMessage(), 'Duplicate entry')) { + send_error_message("User already exists in CtrlPanel\'s Database"); + } else { + send_error_message("Something went wrong when communicating with the Database."); + } + exit(); + } +} +?> diff --git a/public/installer/src/forms/dashboard.php b/public/installer/src/forms/dashboard.php index e69de29bb..2229d4133 100644 --- a/public/installer/src/forms/dashboard.php +++ b/public/installer/src/forms/dashboard.php @@ -0,0 +1,29 @@ +<?php + + +if (isset($_POST['checkGeneral'])) { + wh_log('setting app settings', 'debug'); + $appname = '"' . $_POST['name'] . '"'; + $appurl = $_POST['url']; + + $parsedUrl = parse_url($appurl); + + if (!isset($parsedUrl['scheme'])) { + send_error_message("Please set an URL Scheme like 'https://'!"); + exit(); + } + + if (!isset($parsedUrl['host'])) { + send_error_message("Please set an valid URL host like 'https://ctrlpanel.example.com'!"); + exit(); + } + + $appurl = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + + setenv('APP_NAME', $appname); + setenv('APP_URL', $appurl); + + wh_log('App settings set', 'debug'); + next_step(); +} +?> \ No newline at end of file diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index e69de29bb..8e0bdb615 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -0,0 +1,62 @@ +<?php + +mysqli_report(MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ALL); + +if (isset($_POST['checkDB'])) { + $values = [ + //SETTINGS::VALUE => REQUEST-VALUE (coming from the html-form) + 'DB_HOST' => 'databasehost', + 'DB_DATABASE' => 'database', + 'DB_USERNAME' => 'databaseuser', + 'DB_PASSWORD' => 'databaseuserpass', + 'DB_PORT' => 'databaseport', + 'DB_CONNECTION' => 'databasedriver', + ]; + + wh_log('Trying to connect to the Database', 'debug'); + + try { + $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); + } catch (mysqli_sql_exception $e) { + wh_log($e->getMessage(), 'error'); + send_error_message($e->getMessage()); + exit(); + } + + foreach ($values as $key => $value) { + $param = $_POST[$value]; + setenv($key, $param); + } + + wh_log('Database connection successful', 'debug'); + next_step(); +} + +if (isset($_POST['feedDB'])) { + wh_log('Feeding the Database', 'debug'); + $logs = ''; + + try { + //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); + //$logs .= run_console('composer install --no-dev --optimize-autoloader'); + if (!str_contains(getenv('APP_KEY'), 'base64')) { + $logs .= run_console('php artisan key:generate --force'); + } else { + $logs .= "Key already exists. Skipping\n"; + } + $logs .= run_console('php artisan storage:link'); + $logs .= run_console('php artisan migrate --seed --force'); + $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); + $logs .= run_console('php artisan db:seed --class=PermissionsSeeder --force'); + + wh_log($logs, 'debug'); + + wh_log('Feeding the Database successful', 'debug'); + next_step(); + } catch (Throwable $th) { + wh_log('Feeding the Database failed', 'error'); + send_error_message("Feeding the Database failed"); + } +} + +?> diff --git a/public/installer/src/forms/pterodactyl.php b/public/installer/src/forms/pterodactyl.php index e69de29bb..d4c24dd94 100644 --- a/public/installer/src/forms/pterodactyl.php +++ b/public/installer/src/forms/pterodactyl.php @@ -0,0 +1,80 @@ +<?php +if (isset($_POST['checkPtero'])) { + wh_log('Checking Pterodactyl Settings', 'debug'); + + $url = $_POST['url']; + $key = $_POST['key']; + $clientkey = $_POST['clientkey']; + + $parsedUrl = parse_url($url); + + if (!isset($parsedUrl['scheme'])) { + send_error_message("Please set an URL Scheme like 'https://'!"); + exit(); + } + + if (!isset($parsedUrl['host'])) { + send_error_message("Please set an valid URL host like 'https://panel.example.com'!"); + exit(); + } + + $url = $parsedUrl['scheme'] . '://' . $parsedUrl['host']; + + $callpteroURL = $url . '/api/client/account'; + $call = curl_init(); + + curl_setopt($call, CURLOPT_URL, $callpteroURL); + curl_setopt($call, CURLOPT_RETURNTRANSFER, true); + curl_setopt($call, CURLOPT_HTTPHEADER, [ + 'Accept: Application/vnd.pterodactyl.v1+json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $clientkey, + ]); + $callresponse = curl_exec($call); + $callresult = json_decode($callresponse, true); + curl_close($call); + + $pteroURL = $url . '/api/application/users'; + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $pteroURL); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Accept: Application/vnd.pterodactyl.v1+json', + 'Content-Type: application/json', + 'Authorization: Bearer ' . $key, + ]); + $response = curl_exec($ch); + $result = json_decode($response, true); + curl_close($ch); + + if (!is_array($result)) { + wh_log('No array in response found', 'error'); + send_error_message("An unknown Error occured, please try again!"); + } + + if (array_key_exists('errors', $result) && $result['errors'][0]['detail'] === 'This action is unauthorized.') { + wh_log('API CALL ERROR: ' . $result['errors'][0]['code'], 'error'); + send_error_message("Couldn\'t connect to Pterodactyl. Make sure your Application API key has all read and write permissions!"); + exit(); + } + + if (array_key_exists('errors', $callresult) && $callresult['errors'][0]['detail'] === 'Unauthenticated.') { + wh_log('API CALL ERROR: ' . $callresult['errors'][0]['code'], 'error'); + send_error_message("Your ClientAPI Key is wrong or the account is not an admin!"); + exit(); + } + + try { + run_console("php artisan settings:set 'PterodactylSettings' 'panel_url' '$url'"); + run_console("php artisan settings:set 'PterodactylSettings' 'admin_token' '$key'"); + run_console("php artisan settings:set 'PterodactylSettings' 'user_token' '$clientkey'"); + wh_log('Database updated', 'debug'); + next_step(); + } catch (Throwable $th) { + wh_log("Setting Pterodactyl information failed.", 'error'); + send_error_message($th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs!"); + exit(); + } +} +?> \ No newline at end of file diff --git a/public/installer/src/forms/redis.php b/public/installer/src/forms/redis.php index e69de29bb..6837df3ae 100644 --- a/public/installer/src/forms/redis.php +++ b/public/installer/src/forms/redis.php @@ -0,0 +1,32 @@ +<?php + + +if (isset($_POST['redisSetup'])) { + wh_log('Setting up Redis', 'debug'); + $redisHost = $_POST['redishost']; + $redisPort = $_POST['redisport']; + $redisPassword = $_POST['redispassword']; + + $redisClient = new Client([ + 'host' => $redisHost, + 'port' => $redisPort, + 'password' => $redisPassword, + 'timeout' => 1.0, + ]); + + try { + $redisClient->ping(); + + setenv('MEMCACHED_HOST', $redisHost); + setenv('REDIS_HOST', $redisHost); + setenv('REDIS_PORT', $redisPort); + setenv('REDIS_PASSWORD', ($redisPassword === '' ? 'null' : $redisPassword)); + + wh_log('Redis connection successful. Settings updated.', 'debug'); + next_step(); + } catch (Throwable $th) { + wh_log('Redis connection failed. Settings updated.', 'debug'); + send_error_message("Please check your credentials!<br>" . $th->getMessage()); + } +} +?> diff --git a/public/installer/src/forms/smtp.php b/public/installer/src/forms/smtp.php index e69de29bb..0ec801da9 100644 --- a/public/installer/src/forms/smtp.php +++ b/public/installer/src/forms/smtp.php @@ -0,0 +1,71 @@ +<?php + +use PHPMailer\PHPMailer\Exception; +use PHPMailer\PHPMailer\PHPMailer; +use Predis\Client; + +require './src/phpmailer/Exception.php'; +require './src/phpmailer/PHPMailer.php'; +require './src/phpmailer/SMTP.php'; + + +if (isset($_POST['checkSMTP'])) { + wh_log('Checking SMTP Settings', 'debug'); + try { + $mail = new PHPMailer(true); + + //Server settings + // Send using SMTP + $mail->isSMTP(); + $mail->Host = $_POST['host']; + // Enable SMTP authentication + $mail->SMTPAuth = true; + $mail->Username = $_POST['user']; + $mail->Password = $_POST['pass']; + $mail->SMTPSecure = $_POST['encryption']; + $mail->Port = (int) $_POST['port']; + + // Test E-mail metadata + $mail->setFrom($_POST['user'], $_POST['user']); + $mail->addAddress($_POST['user'], $_POST['user']); + + // Content + // Set email format to HTML + $mail->isHTML(true); + $mail->Subject = 'It Worked! - Test E-Mail from Ctrlpanel.gg'; + $mail->Body = 'Your E-Mail Settings are correct!'; + + $mail->send(); + } catch (Exception $e) { + wh_log($mail->ErrorInfo, 'error'); + send_error_message("Something went wrong while sending test E-Mail!<br>" . $mail->ErrorInfo); + exit(); + } + + wh_log('SMTP Settings are correct', 'debug'); + wh_log('Updating Database', 'debug'); + $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'), getenv('DB_PORT')); + if ($db->connect_error) { + wh_log($db->connect_error, 'error'); + send_error_message("Could not connect to the Database"); + exit(); + } + $values = [ + 'mail_mailer' => $_POST['method'], + 'mail_host' => $_POST['host'], + 'mail_port' => $_POST['port'], + 'mail_username' => $_POST['user'], + 'mail_password' => $_POST['pass'], + 'mail_encryption' => $_POST['encryption'], + 'mail_from_address' => $_POST['user'], + ]; + + foreach ($values as $key => $value) { + run_console("php artisan settings:set 'MailSettings' '$key' '$value'"); + } + + wh_log('Database updated', 'debug'); + next_step(); +} + +?> \ No newline at end of file diff --git a/public/installer/src/forms/timezone.php b/public/installer/src/forms/timezone.php index e69de29bb..b3ff5fa2c 100644 --- a/public/installer/src/forms/timezone.php +++ b/public/installer/src/forms/timezone.php @@ -0,0 +1,13 @@ +<?php + +if (isset($_POST['timezoneConfig'])) { + wh_log('Setting up Timezone', 'debug'); + $timezone = $_POST['timezone']; + + setenv('APP_TIMEZONE', $timezone); + + wh_log('Timezone set: ' . $timezone, 'debug'); + next_step(); +} + +?> \ No newline at end of file diff --git a/public/installer/src/functions/database.php b/public/installer/src/functions/database.php deleted file mode 100644 index e69de29bb..000000000 diff --git a/public/installer/src/functions/environment.php b/public/installer/src/functions/environment.php index e69de29bb..3ae262c39 100644 --- a/public/installer/src/functions/environment.php +++ b/public/installer/src/functions/environment.php @@ -0,0 +1,132 @@ +<?php + +/** +* Sets the environment variable into the env file + * @param string $envKey The environment key to set or modify + * @param string $envValue The environment variable to set + * @return bool true on success or false on failure. + */ +function setenv($envKey, $envValue) +{ + $envFile = dirname(__FILE__, 3) . '/.env'; + $str = file_get_contents($envFile); + + $str .= "\n"; // In case the searched variable is in the last line without \n + $keyPosition = strpos($str, "{$envKey}="); + $endOfLinePosition = strpos($str, PHP_EOL, $keyPosition); + $oldLine = substr($str, $keyPosition, $endOfLinePosition - $keyPosition); + $str = str_replace($oldLine, "{$envKey}={$envValue}", $str); + $str = substr($str, 0, -1); + + $fp = fopen($envFile, 'w'); + fwrite($fp, $str); + fclose($fp); +} + + +$required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl', 'redis']; + +/** + * Check all extensions to see if they have loaded or not + * @return array Returns an array of extensions that failed to load. + */ +function checkExtensions(): array +{ + global $required_extensions; + + wh_log('checking extensions', 'debug'); + + $not_ok = []; + $extentions = get_loaded_extensions(); + + foreach ($required_extensions as $ext) { + if (!preg_grep('/^(?=.*' . $ext . ').*$/', $extentions)) { + array_push($not_ok, $ext); + } + } + + wh_log('loaded extensions:', 'debug', $extentions); + wh_log('failed extensions:', 'debug', $not_ok); + return $not_ok; +} + +$requirements = [ + 'minPhp' => '8.1', + 'maxPhp' => '8.4', // This version is not supported + 'mysql' => '5.7.22', +]; + +/** + * Check if the minimum PHP version is present + * @return string 'OK' on success and 'not OK' on failure. + */ +function checkPhpVersion(): string +{ + global $requirements; + + wh_log('php version: ' . phpversion(), 'debug'); + if (version_compare(phpversion(), $requirements['minPhp'], '>=') && version_compare(phpversion(), $requirements['maxPhp'], '<=')) { + return 'OK'; + } + + return 'not OK'; +} + +/** + * Check if zip is installed using a shell command + * @return string 'OK' on success and 'not OK' on failure. + */ +function getZipVersion(): string +{ + wh_log('attempting to get zip version', 'debug'); + $output = shell_exec('zip -v') ?? ''; + preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); + + $versionoutput = $version[0] ?? 0; + wh_log('zip version: ' . $versionoutput, 'debug'); + + return $versionoutput != 0 ? 'OK' : 'not OK'; +} + +/** + * Check if git is installed using a shell command + * @return string 'OK' on success and 'not OK' on failure. + */ +function getGitVersion(): string +{ + wh_log('attempting to get git version', 'debug'); + $output = shell_exec('git --version') ?? ''; + preg_match('@[0-9]+\.[0-9]+\.[0-9]+@', $output, $version); + + $versionoutput = $version[0] ?? 0; + wh_log('git version: ' . $versionoutput, 'debug'); + + return $versionoutput != 0 ? 'OK' : 'not OK'; +} + +/** + * Check if tar is installed using a shell command + * @return string 'OK' on success and 'not OK' on failure. + */ +function getTarVersion(): string +{ + wh_log('attempting to get tar version', 'debug'); + $output = shell_exec('tar --version') ?? ''; + preg_match('@[0-9]+\.[0-9]+@', $output, $version); + + $versionoutput = $version[0] ?? 0; + wh_log('tar version: ' . $versionoutput, 'debug'); + + return $versionoutput != 0 ? 'OK' : 'not OK'; +} + +/** + * Check if the environment file is writable + * @return bool Returns true on writable and false on not writable. + */ +function checkWriteable(): bool +{ + return is_writable('../../.env'); +} + +?> diff --git a/public/installer/src/functions/installer.php b/public/installer/src/functions/installer.php new file mode 100644 index 000000000..24cbf5954 --- /dev/null +++ b/public/installer/src/functions/installer.php @@ -0,0 +1,16 @@ +<?php + +function send_error_message(string $message): void +{ + $_SESSION['error-message'] = $message; + header("LOCATION: index.php"); + exit(); +} + +function next_step(): void +{ + $_SESSION['installation_step']++; + header("LOCATION: index.php"); +} + +?> diff --git a/public/installer/src/functions/logging.php b/public/installer/src/functions/logging.php index e69de29bb..f8151c4ec 100644 --- a/public/installer/src/functions/logging.php +++ b/public/installer/src/functions/logging.php @@ -0,0 +1,45 @@ +<?php + +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; + +/** + * Log to the default laravel.log file + * @param string $message The message to log + * @param string $level The log level to use (debug, info, warning, error, critical) + * @param array $context [optional] The context to log extra information + * @return void + */ +function wh_log(string $message, string $level = 'info', array $context = []): void +{ + $formatter = new LineFormatter(null, null, true, true); + $stream = new StreamHandler(dirname(__FILE__, 3) . '../../storage/logs/installer.log', Logger::DEBUG); + $stream->setFormatter($formatter); + + $log = new Logger('CtrlPanel'); + $log->pushHandler($stream); + + switch (strtolower($level)) { + case 'debug': // Only log debug messages if APP_DEBUG is true + if (getenv('APP_DEBUG') === false) return; + $log->debug($message, $context); + break; + case 'info': + $log->info($message, $context); + break; + case 'warning': + $log->warning($message, $context); + break; + case 'error': + $log->error($message, $context); + break; + case 'critical': + $log->critical($message, $context); + break; + } + // Prevent memory leaks by resetting the logger + $log->reset(); +} + +?> diff --git a/public/installer/src/functions/shell.php b/public/installer/src/functions/shell.php index e69de29bb..c0ebb8e62 100644 --- a/public/installer/src/functions/shell.php +++ b/public/installer/src/functions/shell.php @@ -0,0 +1,41 @@ +<?php + +/** + * Run a shell command + * @param string $command The command string to run + * @param array|null $descriptors [optional]<p> + * An indexed array where the key represents the descriptor number and the value represents how PHP will pass that descriptor to the child process. 0 is stdin, 1 is stdout, while 2 is stderr. + * Default descriptors when null are 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] + * </p> + * @param string|null $cwd [optional] <p> + * The initial working dir for the command. This must be an + * absolute directory path, or null + * if you want to use the default value (the working dir of the current + * PHP process) + * </p> + * @param array|null $options [optional] <p> + * Allows you to specify additional options. + * @link https://www.php.net/manual/en/function.proc-open.php proc_open + * </p> + * @return false|string|null Returns the result from the command. + */ +function run_console(string $command, array $descriptors = null, string $cwd = null, array $options = null) +{ + wh_log('running command: ' . $command, 'debug'); + + $path = dirname(__FILE__, 3); + $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); + $output = stream_get_contents($pipes[1]); + $exit_code = proc_close($handle); + + if ($exit_code > 0) { + wh_log('command result: ' . $output, 'error'); + throw new Exception("There was an error after running command `$command`", $exit_code); + return $output; + } else { + return $output; + } +} + +?> diff --git a/public/installer/src/functions/utils.php b/public/installer/src/functions/utils.php index e69de29bb..381064027 100644 --- a/public/installer/src/functions/utils.php +++ b/public/installer/src/functions/utils.php @@ -0,0 +1,41 @@ +<?php + +function determineIfRunningInDocker(): bool +{ + return file_exists('/.dockerenv'); +} + +/** + * Encrypt the given value + * @param mixed $value The variable to be encrypted + * @param bool $serialize If the encryption should be serialized + * @return string Returns the encrypted variable. + */ +function encryptSettingsValue(mixed $value, $serialize = true): string +{ + $appKey = getenv('APP_KEY'); + $appKey = base64_decode(Str::after($appKey, 'base64:')); + $encrypter = new Encrypter($appKey, 'AES-256-CBC'); + $encryptedKey = $encrypter->encrypt($value, $serialize); + + return $encryptedKey; +} + +/** + * Decrypt the given value + * @param mixed $payload The payload to be decrypted + * @param bool $unserialize If the encryption should be unserialized + * @return mixed Returns the decrypted variable on success, throws otherwise. + */ + +function decryptSettingsValue(mixed $payload, $unserialize = true) +{ + $appKey = getenv('APP_KEY'); + $appKey = base64_decode(Str::after($appKey, 'base64:')); + $encrypter = new Encrypter($appKey, 'AES-256-CBC'); + $decryptedKey = $encrypter->decrypt($payload, $unserialize); + + return $decryptedKey; +} + +?> diff --git a/public/installer/views/admin-creation.php b/public/installer/views/admin-creation.php index b2eb9fa4f..19fe8c83e 100644 --- a/public/installer/views/admin-creation.php +++ b/public/installer/views/admin-creation.php @@ -6,10 +6,10 @@ $subtitle = "Lets create the first admin user!" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="createUser"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="createUser"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="form-group"> diff --git a/public/installer/views/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php index 642b0ddf5..76e6fc0b8 100644 --- a/public/installer/views/dashboard-configuration.php +++ b/public/installer/views/dashboard-configuration.php @@ -5,10 +5,10 @@ $title = "Dashboard Configuration" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkGeneral"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="checkGeneral"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/database-configuration.php b/public/installer/views/database-configuration.php index 5aa96dd20..623e92838 100644 --- a/public/installer/views/database-configuration.php +++ b/public/installer/views/database-configuration.php @@ -5,10 +5,10 @@ $title = "Database Configuration" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkDB"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="checkDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index cf12ba40c..cace87f77 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -6,10 +6,10 @@ $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="feedDB"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="feedDB"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="w-full flex justify-center"> diff --git a/public/installer/views/email-configuration.php b/public/installer/views/email-configuration.php index a7545fdae..195a4ad91 100644 --- a/public/installer/views/email-configuration.php +++ b/public/installer/views/email-configuration.php @@ -6,10 +6,10 @@ $subtitle = "This process might take a few seconds when submitted.<br>Please do not refresh or close this page!" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkSMTP"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="checkSMTP"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php index 47ca4acda..83100192f 100644 --- a/public/installer/views/pterodactyl-configuration.php +++ b/public/installer/views/pterodactyl-configuration.php @@ -6,10 +6,10 @@ $subtitle = "Lets get some info about your Pterodactyl Installation!" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="checkPtero"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="checkPtero"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index 76bc67380..24ad5d3b5 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -5,10 +5,10 @@ $title = "Redis Configuration" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="redisSetup"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="redisSetup"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index 0d2228773..4d57cfb1b 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -4,10 +4,10 @@ $title = "Timezone Configuration" ); ?> -<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/forms.php" name="timezoneConfig"> +<form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="timezoneConfig"> - <?php if (isset($_GET['message'])) { - echo "<p class='not-ok check'>" . $_GET['message'] . '</p>'; + <?php if (isset($_SESSION['error-message'])) { + echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> <div class="row"> From 4feec9b7deb8d44af4620efe3fcc0a58ddbbdab0 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 14 Jun 2024 00:00:19 +0200 Subject: [PATCH 432/514] refactor: refactored a lot of thing in the installer and added progress bar and next and previous button, and a footer and fixed some bugs --- public/installer/index.php | 71 +- public/installer/src/forms/admin.php | 1 + public/installer/src/forms/dashboard.php | 4 +- public/installer/src/forms/database.php | 2 - public/installer/src/forms/redis.php | 2 +- .../installer/src/functions/environment.php | 5 +- public/installer/src/functions/installer.php | 2 +- public/installer/src/functions/logging.php | 2 +- public/installer/src/functions/shell.php | 2 +- public/installer/src/functions/utils.php | 10 +- public/installer/styles.css | 968 +++++++++++++++++- public/installer/views/admin-creation.php | 27 +- .../views/dashboard-configuration.php | 27 +- .../views/database-configuration.php | 27 +- public/installer/views/database-migration.php | 27 +- .../installer/views/email-configuration.php | 31 +- .../installer/views/installation-complete.php | 2 +- public/installer/views/layout-bottom.php | 5 + public/installer/views/layout-top.php | 35 +- public/installer/views/mandatory-checks.php | 32 +- .../views/pterodactyl-configuration.php | 27 +- .../installer/views/redis-configuration.php | 27 +- .../views/timezone-configuration.php | 32 +- 23 files changed, 1271 insertions(+), 97 deletions(-) diff --git a/public/installer/index.php b/public/installer/index.php index 4feed33b6..a51c67455 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -1,4 +1,5 @@ <?php +// repport all error ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); @@ -6,17 +7,16 @@ session_start(); use DevCoder\DotEnv; -// use Illuminate\Encryption\Encrypter; -// use Illuminate\Support\Str; -require '../../vendor/autoload.php'; -require 'dotenv.php'; +// Include systems +require_once '../../vendor/autoload.php'; +require_once 'dotenv.php'; +require_once './src/functions/installer.php'; // Include the function files -require_once './src/functions/installer.php'; // very important +require_once './src/functions/logging.php'; require_once './src/functions/environment.php'; require_once './src/functions/shell.php'; -require_once './src/functions/logging.php'; require_once './src/functions/utils.php'; // Include the form files @@ -33,35 +33,52 @@ } if (!file_exists('../../.env')) { - echo run_console('cp .env.example .env'); + echo run_console('cp ../../.env.example ../../.env'); } +// load all the .env value in php env (new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); -$viewNames = [ - 1 => 'mandatory-checks', - 2 => 'timezone-configuration', - 3 => 'database-configuration', - 4 => 'database-migration', - 5 => 'redis-configuration', - 6 => 'dashboard-configuration', - 7 => 'email-configuration', - 8 => 'pterodactyl-configuration', - 9 => 'admin-creation', - 10 => 'installation-complete', +$stepConfig = [ + 1 => ['view' => 'mandatory-checks', 'is_revertable' => false], + 2 => ['view' => 'timezone-configuration', 'is_revertable' => true], + 3 => ['view' => 'database-configuration', 'is_revertable' => true], + 4 => ['view' => 'database-migration', 'is_revertable' => false], + 5 => ['view' => 'redis-configuration', 'is_revertable' => true], + 6 => ['view' => 'dashboard-configuration', 'is_revertable' => true], + 7 => ['view' => 'email-configuration', 'is_revertable' => true], + 8 => ['view' => 'pterodactyl-configuration', 'is_revertable' => false], + 9 => ['view' => 'admin-creation', 'is_revertable' => false], + 10 => ['view' => 'installation-complete', 'is_revertable' => false], ]; -// Prioritize $_GET['step'], then session, then default to 1 -$step = isset($_GET['step']) - ? (int)$_GET['step'] // Convert to integer for safety - : (isset($_SESSION['installation_step']) - ? $_SESSION['installation_step'] - : 1); +$_SESSION['last_installation_step'] = count($stepConfig); + +// Initialize or get the current step: +if (!isset($_SESSION['current_installation_step'])) { + // Session variable is not set, initialize it in the SESSION + $_SESSION['current_installation_step'] = 1; +} + +if (isset($_GET['step'])) { + $stepValue = $_GET['step']; + + if (is_numeric($stepValue) && $stepValue >= 1 && $stepValue <= $_SESSION['last_installation_step']) { + // Step is valid numeric within range: + $_SESSION['current_installation_step'] = $stepValue; + } elseif (strtolower($stepValue) === 'next' && $_SESSION['current_installation_step'] < $_SESSION['last_installation_step']) { + // Move to next step: + $_SESSION['current_installation_step']++; + } elseif (strtolower($stepValue) === 'previous' && $_SESSION['current_installation_step'] > 1) { + // Move to previous step: + $_SESSION['current_installation_step']--; + } +} -// Update session with the current step -$_SESSION['installation_step'] = $step; +$viewName = $stepConfig[$_SESSION['current_installation_step']]['view']; -$viewName = $viewNames[$step]; // Get the appropriate view name +// Set previous button availability based on step reversibility +$_SESSION['is_previous_button_available'] = $_SESSION['current_installation_step'] > 1 && $stepConfig[$_SESSION['current_installation_step'] - 1]['is_revertable']; // Load the layout and the specific view file include './views/layout-top.php'; diff --git a/public/installer/src/forms/admin.php b/public/installer/src/forms/admin.php index 9d0df6229..b909f9451 100644 --- a/public/installer/src/forms/admin.php +++ b/public/installer/src/forms/admin.php @@ -93,4 +93,5 @@ exit(); } } + ?> diff --git a/public/installer/src/forms/dashboard.php b/public/installer/src/forms/dashboard.php index 2229d4133..79804edd6 100644 --- a/public/installer/src/forms/dashboard.php +++ b/public/installer/src/forms/dashboard.php @@ -1,6 +1,5 @@ <?php - if (isset($_POST['checkGeneral'])) { wh_log('setting app settings', 'debug'); $appname = '"' . $_POST['name'] . '"'; @@ -26,4 +25,5 @@ wh_log('App settings set', 'debug'); next_step(); } -?> \ No newline at end of file + +?> diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index 8e0bdb615..24d5dc816 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -37,8 +37,6 @@ $logs = ''; try { - //$logs .= run_console(setenv('COMPOSER_HOME', dirname(__FILE__, 3) . '/vendor/bin/composer')); - //$logs .= run_console('composer install --no-dev --optimize-autoloader'); if (!str_contains(getenv('APP_KEY'), 'base64')) { $logs .= run_console('php artisan key:generate --force'); } else { diff --git a/public/installer/src/forms/redis.php b/public/installer/src/forms/redis.php index 6837df3ae..3e9ece35a 100644 --- a/public/installer/src/forms/redis.php +++ b/public/installer/src/forms/redis.php @@ -1,6 +1,5 @@ <?php - if (isset($_POST['redisSetup'])) { wh_log('Setting up Redis', 'debug'); $redisHost = $_POST['redishost']; @@ -29,4 +28,5 @@ send_error_message("Please check your credentials!<br>" . $th->getMessage()); } } + ?> diff --git a/public/installer/src/functions/environment.php b/public/installer/src/functions/environment.php index 3ae262c39..c98223212 100644 --- a/public/installer/src/functions/environment.php +++ b/public/installer/src/functions/environment.php @@ -8,7 +8,9 @@ */ function setenv($envKey, $envValue) { - $envFile = dirname(__FILE__, 3) . '/.env'; + $rootDirectory = dirname(__DIR__, 4); + $envFile = $rootDirectory . '/.env'; + $str = file_get_contents($envFile); $str .= "\n"; // In case the searched variable is in the last line without \n @@ -23,7 +25,6 @@ function setenv($envKey, $envValue) fclose($fp); } - $required_extensions = ['openssl', 'gd', 'mysql', 'PDO', 'mbstring', 'tokenizer', 'bcmath', 'xml', 'curl', 'zip', 'intl', 'redis']; /** diff --git a/public/installer/src/functions/installer.php b/public/installer/src/functions/installer.php index 24cbf5954..169931ea4 100644 --- a/public/installer/src/functions/installer.php +++ b/public/installer/src/functions/installer.php @@ -9,7 +9,7 @@ function send_error_message(string $message): void function next_step(): void { - $_SESSION['installation_step']++; + $_SESSION['current_installation_step']++; header("LOCATION: index.php"); } diff --git a/public/installer/src/functions/logging.php b/public/installer/src/functions/logging.php index f8151c4ec..3960ca11f 100644 --- a/public/installer/src/functions/logging.php +++ b/public/installer/src/functions/logging.php @@ -14,7 +14,7 @@ function wh_log(string $message, string $level = 'info', array $context = []): void { $formatter = new LineFormatter(null, null, true, true); - $stream = new StreamHandler(dirname(__FILE__, 3) . '../../storage/logs/installer.log', Logger::DEBUG); + $stream = new StreamHandler(dirname(__DIR__, 4) . '/storage/logs/installer.log', Logger::DEBUG); $stream->setFormatter($formatter); $log = new Logger('CtrlPanel'); diff --git a/public/installer/src/functions/shell.php b/public/installer/src/functions/shell.php index c0ebb8e62..7a72fcedf 100644 --- a/public/installer/src/functions/shell.php +++ b/public/installer/src/functions/shell.php @@ -23,7 +23,7 @@ function run_console(string $command, array $descriptors = null, string $cwd = n { wh_log('running command: ' . $command, 'debug'); - $path = dirname(__FILE__, 3); + $path = dirname(__DIR__, 4); $descriptors = $descriptors ?? [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $handle = proc_open("cd '$path' && bash -c 'exec -a ServerCPP $command'", $descriptors, $pipes, $cwd, null, $options); $output = stream_get_contents($pipes[1]); diff --git a/public/installer/src/functions/utils.php b/public/installer/src/functions/utils.php index 381064027..8f13bb69a 100644 --- a/public/installer/src/functions/utils.php +++ b/public/installer/src/functions/utils.php @@ -1,9 +1,6 @@ <?php -function determineIfRunningInDocker(): bool -{ - return file_exists('/.dockerenv'); -} +use Illuminate\Encryption\Encrypter; /** * Encrypt the given value @@ -38,4 +35,9 @@ function decryptSettingsValue(mixed $payload, $unserialize = true) return $decryptedKey; } +function determineIfRunningInDocker(): bool +{ + return file_exists('/.dockerenv'); +} + ?> diff --git a/public/installer/styles.css b/public/installer/styles.css index 22f5b596a..b7db03605 100644 --- a/public/installer/styles.css +++ b/public/installer/styles.css @@ -1 +1,967 @@ -/*! tailwindcss v3.3.1 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e2e8f0}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#94a3b8}input::placeholder,textarea::placeholder{opacity:1;color:#94a3b8}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.static{position:static}.absolute{position:absolute}.relative{position:relative}.m-0{margin:0}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.box-border{box-sizing:border-box}.block{display:block}.inline{display:inline}.flex{display:flex}.hidden{display:none}.w-1\/3{width:33.333333%}.w-full{width:100%}.min-w-fit{min-width:-moz-fit-content;min-width:fit-content}.list-none{list-style-type:none}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-around{justify-content:space-around}.gap-4{gap:1rem}.gap-8{gap:2rem}.rounded-2xl{border-radius:1rem}.rounded-md{border-radius:.375rem}.border-2{border-width:2px}.border-4{border-width:4px}.border-\[\#2E373B\]{--tw-border-opacity:1;border-color:rgb(46 55 59/var(--tw-border-opacity))}.border-transparent{border-color:#0000}.bg-\[\#1D2125\]{--tw-bg-opacity:1;background-color:rgb(29 33 37/var(--tw-bg-opacity))}.bg-\[\#242A2E\]{--tw-bg-opacity:1;background-color:rgb(36 42 46/var(--tw-bg-opacity))}.bg-green-500\/90{background-color:#22c55ee6}.bg-sky-500{--tw-bg-opacity:1;background-color:rgb(14 165 233/var(--tw-bg-opacity))}.bg-yellow-500\/90{background-color:#eab308e6}.p-6{padding:1.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pt-3{padding-top:.75rem}.text-center{text-align:center}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-bold{font-weight:700}.text-neutral-400{--tw-text-opacity:1;color:rgb(163 163 163/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.shadow-green-400{--tw-shadow-color:#4ade80;--tw-shadow:var(--tw-shadow-colored)}.shadow-sky-400{--tw-shadow-color:#38bdf8;--tw-shadow:var(--tw-shadow-colored)}.shadow-yellow-400{--tw-shadow-color:#facc15;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid #0000;outline-offset:2px}.\[hostname\:port\]{hostname:port}.hover\:bg-green-600:hover{--tw-bg-opacity:1;background-color:rgb(22 163 74/var(--tw-bg-opacity))}.hover\:bg-sky-600:hover{--tw-bg-opacity:1;background-color:rgb(2 132 199/var(--tw-bg-opacity))}.hover\:bg-yellow-600:hover{--tw-bg-opacity:1;background-color:rgb(202 138 4/var(--tw-bg-opacity))}.focus\:border-sky-500:focus{--tw-border-opacity:1;border-color:rgb(14 165 233/var(--tw-border-opacity))}.focus\:outline:focus{outline-style:solid}.focus\:outline-2:focus{outline-width:2px}.focus\:outline-offset-2:focus{outline-offset:2px}.focus\:outline-green-500:focus{outline-color:#22c55e}.focus\:outline-sky-500:focus{outline-color:#0ea5e9}.focus\:outline-yellow-600:focus{outline-color:#ca8a04}@media (min-width:640px){.sm\:w-auto{width:auto}.sm\:min-w-\[550px\]{min-width:550px}} \ No newline at end of file +/* + * You need to have `tailwindcss` packages installed for these commands to work. + * + * Build: `tailwindcss -i tailwind_styles.css -o styles.css -m` + * Dev: `tailwindcss -i tailwind_styles.css -o styles.css --watch` + */ + +/* + ! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com + */ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e2e8f0; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #94a3b8; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #94a3b8; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.bottom-0 { + bottom: 0px; +} + +.m-0 { + margin: 0px; +} + +.mx-2 { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.box-border { + box-sizing: border-box; +} + +.block { + display: block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.hidden { + display: none; +} + +.w-full { + width: 100%; +} + +.cursor-not-allowed { + cursor: not-allowed; +} + +.list-none { + list-style-type: none; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-4 { + gap: 1rem; +} + +.rounded-2xl { + border-radius: 1rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.border { + border-width: 1px; +} + +.border-2 { + border-width: 2px; +} + +.border-4 { + border-width: 4px; +} + +.border-\[\#2E373B\] { + --tw-border-opacity: 1; + border-color: rgb(46 55 59 / var(--tw-border-opacity)); +} + +.border-transparent { + border-color: transparent; +} + +.bg-\[\#1D2125\] { + --tw-bg-opacity: 1; + background-color: rgb(29 33 37 / var(--tw-bg-opacity)); +} + +.bg-\[\#242A2E\] { + --tw-bg-opacity: 1; + background-color: rgb(36 42 46 / var(--tw-bg-opacity)); +} + +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(226 232 240 / var(--tw-bg-opacity)); +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(30 41 59 / var(--tw-bg-opacity)); +} + +.bg-green-500\/90 { + background-color: rgb(34 197 94 / 0.9); +} + +.bg-red-300 { + --tw-bg-opacity: 1; + background-color: rgb(252 165 165 / var(--tw-bg-opacity)); +} + +.bg-sky-500 { + --tw-bg-opacity: 1; + background-color: rgb(14 165 233 / var(--tw-bg-opacity)); +} + +.bg-sky-600 { + --tw-bg-opacity: 1; + background-color: rgb(2 132 199 / var(--tw-bg-opacity)); +} + +.bg-yellow-500\/90 { + background-color: rgb(234 179 8 / 0.9); +} + +.bg-opacity-20 { + --tw-bg-opacity: 0.2; +} + +.p-0 { + padding: 0px; +} + +.p-0\.5 { + padding: 0.125rem; +} + +.p-6 { + padding: 1.5rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.pt-3 { + padding-top: 0.75rem; +} + +.text-center { + text-align: center; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.leading-none { + line-height: 1; +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(100 116 139 / var(--tw-text-opacity)); +} + +.text-neutral-400 { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} + +.text-sky-100 { + --tw-text-opacity: 1; + color: rgb(224 242 254 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.shadow-inner { + --tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-green-400 { + --tw-shadow-color: #4ade80; + --tw-shadow: var(--tw-shadow-colored); +} + +.shadow-red-200 { + --tw-shadow-color: #fecaca; + --tw-shadow: var(--tw-shadow-colored); +} + +.shadow-sky-400 { + --tw-shadow-color: #38bdf8; + --tw-shadow: var(--tw-shadow-colored); +} + +.shadow-yellow-400 { + --tw-shadow-color: #facc15; + --tw-shadow: var(--tw-shadow-colored); +} + +.outline-none { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.\[hostname\:port\] { + hostname: port; +} + +.hover\:bg-green-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.hover\:bg-red-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(248 113 113 / var(--tw-bg-opacity)); +} + +.hover\:bg-sky-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(2 132 199 / var(--tw-bg-opacity)); +} + +.hover\:bg-yellow-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(202 138 4 / var(--tw-bg-opacity)); +} + +.focus\:border-sky-500:focus { + --tw-border-opacity: 1; + border-color: rgb(14 165 233 / var(--tw-border-opacity)); +} + +.focus\:outline:focus { + outline-style: solid; +} + +.focus\:outline-2:focus { + outline-width: 2px; +} + +.focus\:outline-offset-2:focus { + outline-offset: 2px; +} + +.focus\:outline-green-500:focus { + outline-color: #22c55e; +} + +.focus\:outline-red-500:focus { + outline-color: #ef4444; +} + +.focus\:outline-sky-500:focus { + outline-color: #0ea5e9; +} + +.focus\:outline-yellow-600:focus { + outline-color: #ca8a04; +} + +@media (min-width: 640px) { + .sm\:w-auto { + width: auto; + } + + .sm\:min-w-\[550px\] { + min-width: 550px; + } +} \ No newline at end of file diff --git a/public/installer/views/admin-creation.php b/public/installer/views/admin-creation.php index 19fe8c83e..ef528555f 100644 --- a/public/installer/views/admin-creation.php +++ b/public/installer/views/admin-creation.php @@ -37,10 +37,29 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="createUser">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser"> + Next → </button> </div> </form> diff --git a/public/installer/views/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php index 76e6fc0b8..856f8df45 100644 --- a/public/installer/views/dashboard-configuration.php +++ b/public/installer/views/dashboard-configuration.php @@ -31,10 +31,29 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkGeneral">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral"> + Next → </button> </div> </form> diff --git a/public/installer/views/database-configuration.php b/public/installer/views/database-configuration.php index 623e92838..ca34a4a00 100644 --- a/public/installer/views/database-configuration.php +++ b/public/installer/views/database-configuration.php @@ -64,10 +64,29 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkDB">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next → </button> </div> </form> diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index cace87f77..9962c25c1 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -12,10 +12,29 @@ echo "<p class='not-ok check'>" . $_SESSION['error-message'] . '</p>'; } ?> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="feedDB">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB"> + Next → </button> </div> </form> diff --git a/public/installer/views/email-configuration.php b/public/installer/views/email-configuration.php index 195a4ad91..55ea14363 100644 --- a/public/installer/views/email-configuration.php +++ b/public/installer/views/email-configuration.php @@ -63,17 +63,36 @@ </div> </div> - <div class="flex w-full justify-around mt-4 gap-8 px-8"> - <button type="submit" - class="w-full px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkSMTP">Submit - </button> + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> - <a href="?step=7" class="w-full"> + <a href="?step=next"> <button type="button" class="w-full px-4 py-2 font-bold rounded-md bg-yellow-500/90 hover:bg-yellow-600 shadow-yellow-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-yellow-600"> Skip For Now </button> </a> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP"> + Next → + </button> </div> </form> diff --git a/public/installer/views/installation-complete.php b/public/installer/views/installation-complete.php index c9e04d23d..027784714 100644 --- a/public/installer/views/installation-complete.php +++ b/public/installer/views/installation-complete.php @@ -2,7 +2,7 @@ <!-- top layout here --> <?php -$lockfile = fopen('../../installer.lock', 'w') or exit('Unable to open file!'); +$lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); fwrite($lockfile, 'the installation is locked, delete this file to unlock it'); fclose($lockfile); diff --git a/public/installer/views/layout-bottom.php b/public/installer/views/layout-bottom.php index 35bfcba04..944ac0107 100644 --- a/public/installer/views/layout-bottom.php +++ b/public/installer/views/layout-bottom.php @@ -3,5 +3,10 @@ <!-- any middle view here --> + + <footer class="fixed bottom-0 w-full bg-gray-800 bg-opacity-20 text-center py-2 text-xs"> + © 2024 CtrlPanel | installer v2.0.0 + </footer> + </body> </html> diff --git a/public/installer/views/layout-top.php b/public/installer/views/layout-top.php index 3d2b89382..42003869b 100644 --- a/public/installer/views/layout-top.php +++ b/public/installer/views/layout-top.php @@ -42,18 +42,31 @@ </head> <body class="w-full flex items-center justify-center bg-[#1D2125] text-white"> - <?php - function cardStart($title, $subtitle = null): string - { - return " - <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> - <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> - <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> - <h2 class='text-xl text-center mb-2'>$title</h2>" - . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); - } - ?> +<?php + + + function cardStart($title, $subtitle = null): string + { + // Get total number of steps (you'll need to define this) + $totalSteps = $_SESSION['last_installation_step']; // Assuming you have your $viewNames array defined + // Get current step from session (or default to 1 if not set) + $currentStep = $_SESSION['current_installation_step'] ?? 1; + + // Calculate progress percentage + $progressValue = round(($currentStep / $totalSteps) * 100); + + return " + <div class='flex flex-col gap-4 sm:w-auto w-full sm:min-w-[550px] my-6'> + <h1 class='text-center font-bold text-3xl'>CtrlPanel.gg Installation</h1> + <div class='border-2 border-[#2E373B] bg-[#242A2E] rounded-2xl mx-2'> + <div class='bg-sky-600 text-xs font-medium text-sky-100 text-center p-0.5 leading-none rounded-full' style='width: {$progressValue}%'>Step {$currentStep}</div> + </div> + <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> + <h2 class='text-xl text-center mb-2'>$title</h2>" + . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); +} +?> <!-- any middle view here --> <!-- bottom layout here --> diff --git a/public/installer/views/mandatory-checks.php b/public/installer/views/mandatory-checks.php index 039f378c4..4713c4581 100644 --- a/public/installer/views/mandatory-checks.php +++ b/public/installer/views/mandatory-checks.php @@ -44,11 +44,31 @@ </ul> -<a href="?step=2" class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> - Lets go! - </button> -</a> +<hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + +<div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + <a href="?step=next"> + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="mandatory"> + Next → + </button> + </a> +</div> <!-- bottom layout here --> diff --git a/public/installer/views/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php index 83100192f..5d74bd444 100644 --- a/public/installer/views/pterodactyl-configuration.php +++ b/public/installer/views/pterodactyl-configuration.php @@ -41,10 +41,29 @@ </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="checkPtero">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero"> + Next → </button> </div> </form> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index 24ad5d3b5..04e4586d3 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -40,10 +40,29 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="redisSetup">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="redisSetup"> + Next → </button> </div> </form> diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index 4d57cfb1b..7806c76b2 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -15,8 +15,7 @@ <div class="form-group"> <div class="flex flex-col mb-3"> <label for="timezone">Timezone</label> - <select id="timezone" name="timezone" required - class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <select id="timezone" name="timezone" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> <?php foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { if ($timezoneIdentifier === 'UTC') { @@ -31,12 +30,31 @@ class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m </div> </div> - <div class="w-full flex justify-center"> - <button - class="w-1/3 min-w-fit mt-2 px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" - name="timezoneConfig">Submit + <hr style="border: none; height: 3px; background-color: rgba(0, 0, 0, 0.3); border-bottom: 1px; border-radius: 1px; ; margin-top: 30px !important; margin-bottom: 30px"> + + <div class="w-full flex justify-between items-center mt-4"> + <?php + if ($_SESSION['is_previous_button_available'] == true) { + ?> + <a href="?step=previous"> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + ← Back + </button> + </a> + <?php + } else { + ?> + <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + ← Back + </button> + <?php + } + ?> + + <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="timezoneConfig"> + Next → </button> </div> </form> -<!-- bottom layout here --> \ No newline at end of file +<!-- bottom layout here --> From d71d0e89118f28a6ef7fa84321abaf70e42d4755 Mon Sep 17 00:00:00 2001 From: S0ly <matis.mounic@epitech.eu> Date: Fri, 14 Jun 2024 00:06:04 +0200 Subject: [PATCH 433/514] docs: updated the .env.exemple --- .env.example | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index 46f1a273a..813110b54 100644 --- a/.env.example +++ b/.env.example @@ -1,25 +1,40 @@ +# ⚠️ CAUTION: Advanced Configuration ⚠️ +# +# This file (.env) stores sensitive environment variables. +# Modifying these values can significantly impact your application's behavior. +# +# Proceed with caution: +# - Only edit if you have a clear understanding of the specific variable and its purpose. +# - Use the control panel or installer for most configuration changes whenever possible. +# - Keep a backup of this file before making any modifications. +# +# Need Help? Consult the documentation or contact support before making changes. + ### --- App Settings --- ### APP_NAME=CtrlPanel.gg APP_ENV=production APP_KEY= APP_DEBUG=false APP_URL=http://localhost -APP_TIMEZONE=UTC # List with timezones https://www.php.net/manual/en/timezones.php +APP_TIMEZONE=UTC ### --- App Settings End --- ### ### --- Database Settings (required) --- ### +# SQL DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=dashboard DB_USERNAME=dashboarduser DB_PASSWORD= -### --- Database Settings End --- ### -### --- Google Recaptcha Settings --- ### -RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI -RECAPTCHA_SECRET_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe -### --- Google Recaptcha Settings End --- ### +# No-SQL +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 +### --- Database Settings End --- ### ### --- Mail Server Settings --- ### MAIL_MAILER=smtp @@ -55,15 +70,10 @@ PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER=mt1 -### --- External Services Credentials End --- ### - -### --- Additional Configuration --- ### -MEMCACHED_HOST=127.0.0.1 - -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" -### --- Additional Configuration End --- ### + +RECAPTCHA_SITE_KEY=6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI +RECAPTCHA_SECRET_KEY=6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe +### --- External Services Credentials End --- ### From 7a260797b0d1157b94b61c462bef578815fd3101 Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:31:43 +0200 Subject: [PATCH 434/514] Fix Installer migration failing to run because env encryption key is not loaded yet --- public/installer/src/forms/database.php | 30 ++++++++++++++----- public/installer/views/database-migration.php | 4 +-- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index 24d5dc816..5d4917070 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -19,17 +19,38 @@ $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); } catch (mysqli_sql_exception $e) { wh_log($e->getMessage(), 'error'); - send_error_message($e->getMessage()); + header('LOCATION: index.php?step=3&message=' . $e->getMessage()); exit(); } + foreach ($values as $key => $value) { $param = $_POST[$value]; + // if ($key == "DB_PASSWORD") { + // $param = '"' . $_POST[$value] . '"'; + // } setenv($key, $param); } + wh_log('Start APP_KEY generation', 'debug'); + + try { + if (!str_contains(getenv('APP_KEY'), 'base64')) { + $logs = run_console('php artisan key:generate --force'); + wh_log($logs, 'debug'); + + wh_log('Created APP_KEY successful', 'debug'); + } else { + wh_log('Key already exists. Skipping', 'debug'); + } + } catch (Throwable $th) { + wh_log('Creating APP_KEY failed', 'error'); + header("LOCATION: index.php?step=3&message=" . $th->getMessage() . " <br>Please check the installer.log file in /var/www/controlpanel/storage/logs !"); + exit(); + } + wh_log('Database connection successful', 'debug'); - next_step(); + header('LOCATION: index.php?step=3.5'); } if (isset($_POST['feedDB'])) { @@ -37,11 +58,6 @@ $logs = ''; try { - if (!str_contains(getenv('APP_KEY'), 'base64')) { - $logs .= run_console('php artisan key:generate --force'); - } else { - $logs .= "Key already exists. Skipping\n"; - } $logs .= run_console('php artisan storage:link'); $logs .= run_console('php artisan migrate --seed --force'); $logs .= run_console('php artisan db:seed --class=ExampleItemsSeeder --force'); diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index 9962c25c1..85f0e6779 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -2,8 +2,8 @@ <!-- top layout here --> <?php echo cardStart( - $title = "Database Migration and Encryption Key Generation", - $subtitle = "Lets feed your Database and generate some security keys! <br> This process might take a while. Please do not refresh or close this page!" + $title = "Database Migration", + $subtitle = "Lets feed your Database! <br> This process might take a while. Please do not refresh or close this page!" ); ?> <form method="POST" enctype="multipart/form-data" class="m-0" action="/installer/index.php" name="feedDB"> From 3c077b047b5b466e3bb9290265f4d789abb9d2fd Mon Sep 17 00:00:00 2001 From: S0ly <86328249+S0ly@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:24:40 +0200 Subject: [PATCH 435/514] fix: conflicts fix --- public/installer/src/forms/database.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index 5d4917070..f474adfdf 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -19,16 +19,12 @@ $db = new mysqli($_POST['databasehost'], $_POST['databaseuser'], $_POST['databaseuserpass'], $_POST['database'], $_POST['databaseport']); } catch (mysqli_sql_exception $e) { wh_log($e->getMessage(), 'error'); - header('LOCATION: index.php?step=3&message=' . $e->getMessage()); + send_error_message($e->getMessage()); exit(); } - foreach ($values as $key => $value) { $param = $_POST[$value]; - // if ($key == "DB_PASSWORD") { - // $param = '"' . $_POST[$value] . '"'; - // } setenv($key, $param); } @@ -50,7 +46,7 @@ } wh_log('Database connection successful', 'debug'); - header('LOCATION: index.php?step=3.5'); + next_step(); } if (isset($_POST['feedDB'])) { From f48baa96694479878591653b2450d796d43f2209 Mon Sep 17 00:00:00 2001 From: S0ly <86328249+S0ly@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:10:28 +0200 Subject: [PATCH 436/514] fix: nuable bool in tickersettings --- app/Settings/TicketSettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Settings/TicketSettings.php b/app/Settings/TicketSettings.php index 4c7ab5a78..89eec7270 100644 --- a/app/Settings/TicketSettings.php +++ b/app/Settings/TicketSettings.php @@ -6,7 +6,7 @@ class TicketSettings extends Settings { - public ?bool $enabled; + public bool $enabled; public ?string $information; public static function group(): string From b988ade83cddbb11fa78a1778dccfc57717319a8 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 13:14:53 -0600 Subject: [PATCH 437/514] fix: Installer --- public/installer/src/forms/admin.php | 4 ++++ public/installer/src/forms/database.php | 4 ++++ public/installer/src/forms/redis.php | 2 ++ public/installer/src/forms/smtp.php | 3 +++ public/installer/src/functions/logging.php | 3 +++ public/installer/src/functions/utils.php | 3 +++ 6 files changed, 19 insertions(+) diff --git a/public/installer/src/forms/admin.php b/public/installer/src/forms/admin.php index b909f9451..5626e5dc3 100644 --- a/public/installer/src/forms/admin.php +++ b/public/installer/src/forms/admin.php @@ -1,5 +1,9 @@ <?php +use DevCoder\DotEnv; + +(new DotEnv(dirname(__FILE__, 5) . '/.env'))->load(); + if (isset($_POST['createUser'])) { wh_log('Getting Pterodactyl User', 'debug'); diff --git a/public/installer/src/forms/database.php b/public/installer/src/forms/database.php index f474adfdf..cace72f5a 100644 --- a/public/installer/src/forms/database.php +++ b/public/installer/src/forms/database.php @@ -1,5 +1,9 @@ <?php +use DevCoder\DotEnv; + +(new DotEnv(dirname(__FILE__, 5) . '/.env'))->load(); + mysqli_report(MYSQLI_REPORT_STRICT | MYSQLI_REPORT_ALL); if (isset($_POST['checkDB'])) { diff --git a/public/installer/src/forms/redis.php b/public/installer/src/forms/redis.php index 3e9ece35a..882c8ff05 100644 --- a/public/installer/src/forms/redis.php +++ b/public/installer/src/forms/redis.php @@ -1,5 +1,7 @@ <?php +use Predis\Client; + if (isset($_POST['redisSetup'])) { wh_log('Setting up Redis', 'debug'); $redisHost = $_POST['redishost']; diff --git a/public/installer/src/forms/smtp.php b/public/installer/src/forms/smtp.php index 0ec801da9..b2165d7b1 100644 --- a/public/installer/src/forms/smtp.php +++ b/public/installer/src/forms/smtp.php @@ -1,9 +1,12 @@ <?php +use DevCoder\DotEnv; use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\PHPMailer; use Predis\Client; +(new DotEnv(dirname(__FILE__, 5) . '/.env'))->load(); + require './src/phpmailer/Exception.php'; require './src/phpmailer/PHPMailer.php'; require './src/phpmailer/SMTP.php'; diff --git a/public/installer/src/functions/logging.php b/public/installer/src/functions/logging.php index 3960ca11f..70324ccd9 100644 --- a/public/installer/src/functions/logging.php +++ b/public/installer/src/functions/logging.php @@ -1,9 +1,12 @@ <?php +use DevCoder\DotEnv; use Monolog\Formatter\LineFormatter; use Monolog\Handler\StreamHandler; use Monolog\Logger; +(new DotEnv(dirname(__FILE__, 5) . '/.env'))->load(); + /** * Log to the default laravel.log file * @param string $message The message to log diff --git a/public/installer/src/functions/utils.php b/public/installer/src/functions/utils.php index 8f13bb69a..5c13ec524 100644 --- a/public/installer/src/functions/utils.php +++ b/public/installer/src/functions/utils.php @@ -1,7 +1,10 @@ <?php +use DevCoder\DotEnv; use Illuminate\Encryption\Encrypter; +(new DotEnv(dirname(__FILE__, 5) . '/.env'))->load(); + /** * Encrypt the given value * @param mixed $value The variable to be encrypted From 2f41651e3809541c19f8e55678c27992332e89ee Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 18:16:44 -0600 Subject: [PATCH 438/514] feat: Update dependencies --- composer.json | 51 +++++++++++++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index f36209e19..231988a4e 100644 --- a/composer.json +++ b/composer.json @@ -8,51 +8,50 @@ ], "license": "MIT", "require": { - "php": "^8.1", + "php": "^8.2", "ext-intl": "*", "ext-mysqli": "*", "ext-curl": "*", - "biscolab/laravel-recaptcha": "^5.4", - "doctrine/dbal": "^3.5.3", + "biscolab/laravel-recaptcha": "^6.1", + "doctrine/dbal": "^4.0.4", "guzzlehttp/guzzle": "^7.5", "hidehalo/nanoid-php": "^1.1.12", - "kkomelin/laravel-translatable-string-exporter": "^1.18", - "laravel/framework": "^9.50.2", - "laravel/tinker": "^2.8", - "laravel/ui": "^3.4.6", - "laraveldaily/laravel-invoices": "^3.0.2", - "league/flysystem-aws-s3-v3": "^3.12.2", + "kkomelin/laravel-translatable-string-exporter": "^1.22", + "laravel/framework": "^11.17", + "laravel/tinker": "^2.9", + "laravel/ui": "^4.5.2", + "laraveldaily/laravel-invoices": "^4.0.0", + "league/flysystem-aws-s3-v3": "^3.28.0", "paypal/paypal-checkout-sdk": "^1.0.2", - "paypal/rest-api-sdk-php": "^1.14.0", "predis/predis": "*", - "qirolab/laravel-themer": "^2.0.2", + "qirolab/laravel-themer": "^2.3.3", "socialiteproviders/discord": "^4.1.2", - "spatie/laravel-activitylog": "^4.7.3", - "spatie/laravel-permission": "^5.10", - "spatie/laravel-query-builder": "^5.1.2", - "spatie/laravel-settings": "^2.7", - "spatie/laravel-validation-rules": "^3.2.2", + "spatie/laravel-activitylog": "^4.8.0", + "spatie/laravel-permission": "^6.9", + "spatie/laravel-query-builder": "^6.0.1", + "spatie/laravel-settings": "^3.3", + "spatie/laravel-validation-rules": "^3.4.0", "stripe/stripe-php": "^7.128", - "symfony/http-client": "^6.2.6", - "symfony/intl": "^6.2.5", - "symfony/mailgun-mailer": "^6.2.5", - "yajra/laravel-datatables-oracle": "^9.21.2" + "symfony/http-client": "^7.1.2", + "symfony/intl": "^7.1.1", + "symfony/mailgun-mailer": "^7.1.2", + "yajra/laravel-datatables-oracle": "^11.1.3" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.7", + "barryvdh/laravel-debugbar": "^3.13", "fakerphp/faker": "^1.21", - "laravel/sail": "^1.19", + "laravel/sail": "^1.31", "mockery/mockery": "^1.5.1", - "nunomaduro/collision": "^6.4", - "phpunit/phpunit": "^9.6", - "spatie/laravel-ignition": "^1.6" + "nunomaduro/collision": "^8.3", + "phpunit/phpunit": "^11.2", + "spatie/laravel-ignition": "^2.8" }, "config": { "optimize-autoloader": true, "preferred-install": "dist", "sort-packages": true, "platform": { - "php": "8.1" + "php": "8.2" } }, "extra": { From 68e451b4ed5f50871cacc37d8a5e143df87d10cb Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 18:16:58 -0600 Subject: [PATCH 439/514] feat: Change Middlewares to Middleware --- app/Http/Kernel.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index cd7cef5fb..c334a067b 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -72,9 +72,9 @@ class Kernel extends HttpKernel 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'api.token' => ApiAuthToken::class, 'checkSuspended' => CheckSuspended::class, - 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, - 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, - 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, + 'role' => \Spatie\Permission\Middleware\RoleMiddleware::class, + 'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class, + 'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class, ]; } From fcb3cdfe30f0d18b0cc24637480a0184969146a1 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 18:17:21 -0600 Subject: [PATCH 440/514] fix: Change unsignedFloat to float & unsigned --- database/migrations/2014_10_12_000000_create_users_table.php | 2 +- .../migrations/2021_07_09_190453_create_vouchers_table.php | 2 +- .../2021_07_10_062140_update_credits_to_users_table.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index c28f40d8e..b619113e6 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -19,7 +19,7 @@ public function up() $table->id(); $table->string('name'); $table->string('role')->default('member'); - $table->unsignedFloat('credits')->default(250); + $table->float('credits')->default(250)->unsigned(); $table->unsignedInteger('server_limit')->default(1); $table->unsignedInteger('pterodactyl_id')->nullable(); $table->longText('avatar')->nullable(); diff --git a/database/migrations/2021_07_09_190453_create_vouchers_table.php b/database/migrations/2021_07_09_190453_create_vouchers_table.php index 481674b38..5be8937e4 100644 --- a/database/migrations/2021_07_09_190453_create_vouchers_table.php +++ b/database/migrations/2021_07_09_190453_create_vouchers_table.php @@ -17,7 +17,7 @@ public function up() $table->id(); $table->string('code', 36)->unique(); $table->string('memo')->nullable(); - $table->unsignedFloat('credits', 10); + $table->float('credits', 10)->unsigned(); $table->unsignedInteger('uses')->default(1); $table->timestamp('expires_at')->nullable(); $table->timestamps(); diff --git a/database/migrations/2021_07_10_062140_update_credits_to_users_table.php b/database/migrations/2021_07_10_062140_update_credits_to_users_table.php index ef4e069d7..bf106b371 100644 --- a/database/migrations/2021_07_10_062140_update_credits_to_users_table.php +++ b/database/migrations/2021_07_10_062140_update_credits_to_users_table.php @@ -14,7 +14,7 @@ public function up() { Schema::table('users', function (Blueprint $table) { - $table->unsignedFloat('credits', 10)->change(); + $table->float('credits', 10)->change()->unsigned(); }); } @@ -26,7 +26,7 @@ public function up() public function down() { Schema::table('users', function (Blueprint $table) { - $table->unsignedFloat('credits')->change(); + $table->float('credits')->change()->unsigned(); }); } }; From 621485976ad2674273850869affaf570395e6b48 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 18:17:34 -0600 Subject: [PATCH 441/514] fix: Decimal --- database/migrations/2022_07_12_051152_decimals-in-price.php | 2 +- .../2023_05_08_094402_update_user_credits_datatype.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/database/migrations/2022_07_12_051152_decimals-in-price.php b/database/migrations/2022_07_12_051152_decimals-in-price.php index cf1a35b4c..da5c6582d 100644 --- a/database/migrations/2022_07_12_051152_decimals-in-price.php +++ b/database/migrations/2022_07_12_051152_decimals-in-price.php @@ -14,7 +14,7 @@ public function up() { Schema::table('products', function (Blueprint $table) { - $table->decimal('price', ['11', '2'])->change(); + $table->decimal('price', 11, 2)->change(); }); } diff --git a/database/migrations/2023_05_08_094402_update_user_credits_datatype.php b/database/migrations/2023_05_08_094402_update_user_credits_datatype.php index db0acb2f1..57a350377 100644 --- a/database/migrations/2023_05_08_094402_update_user_credits_datatype.php +++ b/database/migrations/2023_05_08_094402_update_user_credits_datatype.php @@ -26,7 +26,7 @@ public function up() public function down() { Schema::table('users', function (Blueprint $table) { - $table->decimal('credits', ['11', '2'])->change(); + $table->decimal('credits', 11, 2)->change(); }); } } From d815adfbb669fc443b0e1594aa7ab1c76e96cf7d Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 18:17:48 -0600 Subject: [PATCH 442/514] fix: Spatie Permission --- ..._04_29_232942_create_permission_tables.php | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/database/migrations/2023_04_29_232942_create_permission_tables.php b/database/migrations/2023_04_29_232942_create_permission_tables.php index 01f845157..499e5fa8a 100644 --- a/database/migrations/2023_04_29_232942_create_permission_tables.php +++ b/database/migrations/2023_04_29_232942_create_permission_tables.php @@ -3,7 +3,6 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; -use Spatie\Permission\PermissionRegistrar; class CreatePermissionTables extends Migration { @@ -52,13 +51,13 @@ public function up() }); Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { - $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + $table->unsignedBigInteger('permission_id'); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); - $table->foreign(PermissionRegistrar::$pivotPermission) + $table->foreign('permission_id') ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); @@ -66,23 +65,23 @@ public function up() $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); - $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], 'permission_id', $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); } else { - $table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + $table->primary(['permission_id', $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); } }); Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { - $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + $table->unsignedBigInteger('role_id'); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); - $table->foreign(PermissionRegistrar::$pivotRole) + $table->foreign('role_id') ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); @@ -90,29 +89,29 @@ public function up() $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); - $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], 'role_id', $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); } else { - $table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + $table->primary(['role_id', $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); } }); Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { - $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); - $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + $table->unsignedBigInteger('permission_id'); + $table->unsignedBigInteger('role_id'); - $table->foreign(PermissionRegistrar::$pivotPermission) + $table->foreign('permission_id') ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); - $table->foreign(PermissionRegistrar::$pivotRole) + $table->foreign('role_id') ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); - $table->primary([PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + $table->primary(['permission_id', 'role_id'], 'role_has_permissions_permission_id_role_id_primary'); }); app('cache') From e98e06936bb4c0687c95d3706ffe172b3d844f10 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 18:18:28 -0600 Subject: [PATCH 443/514] fix: Added missing default --- .../2023_05_08_092704_add_billing_period_to_products.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2023_05_08_092704_add_billing_period_to_products.php b/database/migrations/2023_05_08_092704_add_billing_period_to_products.php index c4a7ac9ac..7936e7fda 100644 --- a/database/migrations/2023_05_08_092704_add_billing_period_to_products.php +++ b/database/migrations/2023_05_08_092704_add_billing_period_to_products.php @@ -23,7 +23,7 @@ public function up() $table->string('billing_period')->default("hourly"); $table->decimal('price', 15, 4)->change(); - $table->decimal('minimum_credits', 15, 4)->change(); + $table->decimal('minimum_credits', 15, 4)->default(-1)->change(); }); DB::statement('UPDATE products SET billing_period="hourly"'); From e814b91030d13bfb99e2281d4ef22f1d2022bf76 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 18:18:40 -0600 Subject: [PATCH 444/514] feat: Added missing default --- ...ault_value_to_locked_in_settings_table.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2024_07_24_150126_add_default_value_to_locked_in_settings_table.php diff --git a/database/migrations/2024_07_24_150126_add_default_value_to_locked_in_settings_table.php b/database/migrations/2024_07_24_150126_add_default_value_to_locked_in_settings_table.php new file mode 100644 index 000000000..47881fc15 --- /dev/null +++ b/database/migrations/2024_07_24_150126_add_default_value_to_locked_in_settings_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->boolean('locked')->default(false)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table) { + $table->boolean('locked')->default(null)->change(); + }); + } +}; From e3f44f190a4f62cc7a274528ae676fbb50c8e33f Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 18:18:58 -0600 Subject: [PATCH 445/514] feat: Added missing nullable & text --- ...add_required_nullable_and_text_columns.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 database/migrations/2024_07_24_175733_add_required_nullable_and_text_columns.php diff --git a/database/migrations/2024_07_24_175733_add_required_nullable_and_text_columns.php b/database/migrations/2024_07_24_175733_add_required_nullable_and_text_columns.php new file mode 100644 index 000000000..41f2c6c0f --- /dev/null +++ b/database/migrations/2024_07_24_175733_add_required_nullable_and_text_columns.php @@ -0,0 +1,46 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::table('locations', function (Blueprint $table) { + $table->text('description')->nullable()->change(); + }); + Schema::table('nodes', function (Blueprint $table) { + $table->text('description')->nullable()->change(); + }); + Schema::table('nests', function (Blueprint $table) { + $table->text('description')->nullable()->change(); + }); + Schema::table('eggs', function (Blueprint $table) { + $table->text('description')->nullable()->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('locations', function (Blueprint $table) { + $table->string('description')->change(); + }); + Schema::table('nodes', function (Blueprint $table) { + $table->string('description')->change(); + }); + Schema::table('nests', function (Blueprint $table) { + $table->string('description')->change(); + }); + Schema::table('eggs', function (Blueprint $table) { + $table->string('description')->change(); + }); + } +}; \ No newline at end of file From 734a1c628211b0155e8a23167612f2c7c9399dcd Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 24 Jul 2024 21:02:24 -0600 Subject: [PATCH 446/514] feat: Re implemented PermissionRegister --- ..._04_29_232942_create_permission_tables.php | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/database/migrations/2023_04_29_232942_create_permission_tables.php b/database/migrations/2023_04_29_232942_create_permission_tables.php index 499e5fa8a..ef728fec0 100644 --- a/database/migrations/2023_04_29_232942_create_permission_tables.php +++ b/database/migrations/2023_04_29_232942_create_permission_tables.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; +use Spatie\Permission\PermissionRegistrar; class CreatePermissionTables extends Migration { @@ -51,13 +52,13 @@ public function up() }); Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { - $table->unsignedBigInteger('permission_id'); + $table->unsignedBigInteger(app(PermissionRegistrar::class)->pivotPermission); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); - $table->foreign('permission_id') + $table->foreign(app(PermissionRegistrar::class)->pivotPermission) ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); @@ -65,23 +66,23 @@ public function up() $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); - $table->primary([$columnNames['team_foreign_key'], 'permission_id', $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], app(PermissionRegistrar::class)->pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); } else { - $table->primary(['permission_id', $columnNames['model_morph_key'], 'model_type'], + $table->primary([app(PermissionRegistrar::class)->pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); } }); Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { - $table->unsignedBigInteger('role_id'); + $table->unsignedBigInteger(app(PermissionRegistrar::class)->pivotRole); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); - $table->foreign('role_id') + $table->foreign(app(PermissionRegistrar::class)->pivotRole) ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); @@ -89,29 +90,29 @@ public function up() $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); - $table->primary([$columnNames['team_foreign_key'], 'role_id', $columnNames['model_morph_key'], 'model_type'], + $table->primary([$columnNames['team_foreign_key'], app(PermissionRegistrar::class)->pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); } else { - $table->primary(['role_id', $columnNames['model_morph_key'], 'model_type'], + $table->primary([app(PermissionRegistrar::class)->pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); } }); Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { - $table->unsignedBigInteger('permission_id'); - $table->unsignedBigInteger('role_id'); + $table->unsignedBigInteger(app(PermissionRegistrar::class)->pivotPermission); + $table->unsignedBigInteger(app(PermissionRegistrar::class)->pivotRole); - $table->foreign('permission_id') + $table->foreign(app(PermissionRegistrar::class)->pivotPermission) ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); - $table->foreign('role_id') + $table->foreign(app(PermissionRegistrar::class)->pivotRole) ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); - $table->primary(['permission_id', 'role_id'], 'role_has_permissions_permission_id_role_id_primary'); + $table->primary([app(PermissionRegistrar::class)->pivotPermission, app(PermissionRegistrar::class)->pivotRole], 'role_has_permissions_permission_id_role_id_primary'); }); app('cache') From df35503b177227317626bc87526d8fc2f9f3ce1e Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Thu, 25 Jul 2024 10:09:14 -0600 Subject: [PATCH 447/514] fix: Store & Update role --- app/Http/Controllers/Admin/RoleController.php | 6 ++++-- app/Http/Controllers/Api/RoleController.php | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index b2c685f44..9d9518555 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -72,7 +72,8 @@ public function store(Request $request): RedirectResponse ]); if ($request->permissions) { - $role->givePermissionTo($request->permissions); + $collectedPermissions = collect($request->permissions)->map(fn($val)=>(int)$val); + $role->givePermissionTo($collectedPermissions); } return redirect() @@ -123,7 +124,8 @@ public function update(Request $request, Role $role) if ($request->permissions) { if($role->id != 1){ //disable admin permissions change - $role->syncPermissions($request->permissions); + $collectedPermissions = collect($request->permissions)->map(fn($val)=>(int)$val); + $role->syncPermissions($collectedPermissions); } } diff --git a/app/Http/Controllers/Api/RoleController.php b/app/Http/Controllers/Api/RoleController.php index ac44c240f..1deca23fe 100644 --- a/app/Http/Controllers/Api/RoleController.php +++ b/app/Http/Controllers/Api/RoleController.php @@ -68,7 +68,8 @@ public function store(Request $request) if ($request->permissions) { $permissions = explode(",",$request->permissions); - foreach($permissions as $permission){ + $collectedPermissions = collect($permissions)->map(fn($val)=>(int)$val); + foreach($collectedPermissions as $permission){ $role->givePermissionTo($permission); } } @@ -124,7 +125,8 @@ public function update(Request $request, int $id) if ($request->permissions) { $permissions = explode(",",$request->permissions); - $role->syncPermissions($permissions); + $collectedPermissions = collect($permissions)->map(fn($val)=>(int)$val); + $role->syncPermissions($collectedPermissions); } From 1ee50cc8497320c02cc4e81d9c37e4485777dd6c Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Thu, 25 Jul 2024 10:09:31 -0600 Subject: [PATCH 448/514] fix: User update & store rol --- app/Http/Controllers/Admin/UserController.php | 3 ++- app/Http/Controllers/Api/UserController.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 69e3cbd8b..57fc53cd2 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -159,7 +159,8 @@ public function update(Request $request, User $user) //update roles if ($request->roles && $this->can(self::CHANGE_ROLE_PERMISSION)) { - $user->syncRoles($request->roles); + $collectedRoles = collect($request->roles)->map(fn($val)=>(int)$val); + $user->syncRoles($collectedRoles); } if (isset($this->pterodactyl->getUser($request->input('pterodactyl_id'))['errors'])) { diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php index cb7a1bb79..3f2a281f7 100644 --- a/app/Http/Controllers/Api/UserController.php +++ b/app/Http/Controllers/Api/UserController.php @@ -106,7 +106,8 @@ public function update(Request $request, int $id) ]); } if($request->has("role")){ - $user->syncRoles($request->role); + $collectedRoles = collect($request->role)->map(fn($val)=>(int)$val); + $user->syncRoles($collectedRoles); } $user->update($request->except('role')); From b4baf79ec5762a8145fb371e0e5896e6933519cc Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Thu, 25 Jul 2024 10:09:50 -0600 Subject: [PATCH 449/514] feat: Removed default value migration --- ...ault_value_to_locked_in_settings_table.php | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 database/migrations/2024_07_24_150126_add_default_value_to_locked_in_settings_table.php diff --git a/database/migrations/2024_07_24_150126_add_default_value_to_locked_in_settings_table.php b/database/migrations/2024_07_24_150126_add_default_value_to_locked_in_settings_table.php deleted file mode 100644 index 47881fc15..000000000 --- a/database/migrations/2024_07_24_150126_add_default_value_to_locked_in_settings_table.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php - -use Illuminate\Database\Migrations\Migration; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Schema; - -return new class extends Migration -{ - /** - * Run the migrations. - * - * @return void - */ - public function up(): void - { - Schema::table('settings', function (Blueprint $table) { - $table->boolean('locked')->default(false)->change(); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down(): void - { - Schema::table('settings', function (Blueprint $table) { - $table->boolean('locked')->default(null)->change(); - }); - } -}; From 6d6c20a8f680c6fc89c5b1e78d993bc34a1a93f6 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Thu, 25 Jul 2024 10:10:51 -0600 Subject: [PATCH 450/514] feat: Added migration --- ...712_update_settings_default_and_unique.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2024_07_25_083712_update_settings_default_and_unique.php diff --git a/database/migrations/2024_07_25_083712_update_settings_default_and_unique.php b/database/migrations/2024_07_25_083712_update_settings_default_and_unique.php new file mode 100644 index 000000000..e4b421781 --- /dev/null +++ b/database/migrations/2024_07_25_083712_update_settings_default_and_unique.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::table('settings', function (Blueprint $table): void { + $table->boolean('locked')->default(false)->change(); + $table->unique(['group', 'name']); + $table->dropIndex(['group']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('settings', function (Blueprint $table): void { + $table->boolean('locked')->default(null)->change(); + $table->dropUnique(['group', 'name']); + $table->index('group'); + }); + } +}; From 727d6351383c6077214aece09f47aba6d71ac876 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Thu, 25 Jul 2024 10:15:58 -0600 Subject: [PATCH 451/514] feat: Removed void --- .../2024_07_25_083712_update_settings_default_and_unique.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/migrations/2024_07_25_083712_update_settings_default_and_unique.php b/database/migrations/2024_07_25_083712_update_settings_default_and_unique.php index e4b421781..ad81ce1ee 100644 --- a/database/migrations/2024_07_25_083712_update_settings_default_and_unique.php +++ b/database/migrations/2024_07_25_083712_update_settings_default_and_unique.php @@ -11,7 +11,7 @@ */ public function up(): void { - Schema::table('settings', function (Blueprint $table): void { + Schema::table('settings', function (Blueprint $table) { $table->boolean('locked')->default(false)->change(); $table->unique(['group', 'name']); $table->dropIndex(['group']); @@ -23,7 +23,7 @@ public function up(): void */ public function down(): void { - Schema::table('settings', function (Blueprint $table): void { + Schema::table('settings', function (Blueprint $table) { $table->boolean('locked')->default(null)->change(); $table->dropUnique(['group', 'name']); $table->index('group'); From b5f6b0d30f2e69633198de03b5cbc92cd417f85e Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Thu, 25 Jul 2024 13:54:25 -0600 Subject: [PATCH 452/514] fix: Showing roles & Design --- .../default/views/admin/roles/index.blade.php | 136 +++++++++--------- 1 file changed, 66 insertions(+), 70 deletions(-) diff --git a/themes/default/views/admin/roles/index.blade.php b/themes/default/views/admin/roles/index.blade.php index 4391f662b..0486f6279 100644 --- a/themes/default/views/admin/roles/index.blade.php +++ b/themes/default/views/admin/roles/index.blade.php @@ -11,92 +11,88 @@ <div class="col-sm-6"> <ol class="breadcrumb float-sm-right"> <li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li> - <li class="breadcrumb-item"><a class="text-muted" href="{{route('admin.roles.index')}}">{{__('Roles List')}}</a></li> + <li class="breadcrumb-item"><a class="text-muted" + href="{{route('admin.roles.index')}}">{{__('Roles List')}}</a></li> </ol> </div> </div> </div> </section> - <div class="py-4 main"> - - @can('admin.roles.write') - <div class="my-3 d-flex justify-content-end"> - <a href="{{route('admin.roles.create')}}" class="btn btn-primary"><i - class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> - </div> - @endcan - - <div class="border-0 shadow card card-body table-wrapper table-responsive"> - <h2 class="mb-4 h5">{{ __('Roles') }}</h2> - <section class="content"> <div class="container-fluid"> + <div class="card"> + <div class="card-header"> <div class="d-flex justify-content-between"> <h5 class="card-title"><i class="mr-2 fas fa-user-check"></i>{{__('Roles List')}}</h5> + @can('admin.roles.write') + <a href="{{route('admin.roles.create')}}" class="float-right btn btn-primary"><i + class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> + @endcan </div> - @can('admin.roles.write') - <a href="{{route('admin.roles.create')}}" class="float-right btn btn-primary"><i class="fa fas fa-shield-alt pe-2"></i>{{__('Create role')}}</a> - @endcan </div> + <div class="card-body table-responsive"> - <div class="card-body table-responsive"> - <table id="datatable" class="table table-striped"> - <thead> - <tr> - <th>{{__("ID")}}</th> - <th>{{__("Name")}}</th> - <th>{{__("User count")}}</th> - <th>{{__("Permissions count")}}</th> - <th>{{__("Power")}}</th> - <th>{{__("Actions")}}</th> - </tr> - </thead> - <tbody> - </tbody> - </table> - </div> - </div> + <table id="datatable" class="table table-striped"> + <thead> + <tr> + <th>{{__("ID")}}</th> + <th>{{__("Name")}}</th> + <th>{{__("User count")}}</th> + <th>{{__("Permissions count")}}</th> + <th>{{__("Power")}}</th> + <th>{{__("Actions")}}</th> + </tr> + </thead> + <tbody> + </tbody> + </table> + </div> </div> - </div> - @endsection - <script> - document.addEventListener("DOMContentLoaded", function() { - $('#datatable').DataTable({ - language: { - url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("SETTINGS::LOCALE:DATATABLES")}}.json' - }, - processing: true, - serverSide: true, //increases loading times too much? change back to "true" if it does - stateSave: true, - ajax: "{{route('admin.roles.datatable')}}", - columns: [{ - data: 'id' - }, - { - data: 'name' - }, - { - data: 'users_count' - }, - { - data: 'permissions_count' - }, - { - data: 'power' - }, - { - data: 'actions', - sortable: false - } - ], - fnDrawCallback: function(oSettings) { - $('[data-toggle="popover"]').popover(); - } - }); - </script> + + </div> + <!-- END CUSTOM CONTENT --> +</section> +<!-- END CONTENT --> +<script> + document.addEventListener("DOMContentLoaded", function () { + $('#datatable').DataTable({ + language: { + url: '//cdn.datatables.net/plug-ins/1.11.3/i18n/{{config("SETTINGS::LOCALE:DATATABLES")}}.json' + }, + processing: true, + serverSide: true, //increases loading times too much? change back to "true" if it does + stateSave: true, + ajax: "{{route('admin.roles.datatable')}}", + columns: [{ + data: 'id' + }, + { + data: 'name' + }, + { + data: 'users_count' + }, + { + data: 'permissions_count' + }, + { + data: 'power' + }, + { + data: 'actions', + sortable: false + } + ], + fnDrawCallback: function (oSettings) { + $('[data-toggle="popover"]').popover(); + } + }); + }); +</script> +@endsection From 8fa57efc025ad935f9182ae2e705b41b674859a3 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Thu, 25 Jul 2024 14:05:09 -0600 Subject: [PATCH 453/514] feat: Added power update --- app/Http/Controllers/Admin/RoleController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index 9d9518555..3ce5ad5bb 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -136,7 +136,8 @@ public function update(Request $request, Role $role) //}else{ $role->update([ 'name' => $request->name, - 'color' => $request->color + 'color' => $request->color, + 'power' => $request->power ]); //} From f739fa1352c3c3f8a53e73d46d7ab82baff96c2e Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 27 Jul 2024 18:35:14 -0600 Subject: [PATCH 454/514] fix: Permission name --- config/permissions_web.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/permissions_web.php b/config/permissions_web.php index b4037aac2..1e67f65fc 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -13,7 +13,7 @@ 'Delete Role' => 'admin.roles.delete', - 'View Tickets' => 'admin.ticket.read', + 'View Tickets' => 'admin.tickets.read', 'Manage Ticket' => 'admin.tickets.write', 'Receive Ticket Notifications' => 'admin.tickets.get_notification', From d2b88a5694dbc086bd55cca0b8a9e9ecd2d112ea Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 27 Jul 2024 20:23:23 -0600 Subject: [PATCH 455/514] feat: Update roles theme --- .../default/views/admin/roles/edit.blade.php | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/themes/default/views/admin/roles/edit.blade.php b/themes/default/views/admin/roles/edit.blade.php index 76dfc3cdd..5af3378d1 100644 --- a/themes/default/views/admin/roles/edit.blade.php +++ b/themes/default/views/admin/roles/edit.blade.php @@ -1,12 +1,36 @@ @extends('layouts.main') @section('content') - <div class="py-4 main"> - - <div class="border-0 shadow card card-body table-wrapper table-responsive"> - <h2 class="mb-4 h5">{{ isset($role) ? __('Edit role') : __('Create role') }}</h2> - - <form method="post" + <section class="content-header"> + <div class="container-fluid"> + <div class="mb-2 row"> + <div class="col-sm-6"> + <h1>{{ isset($role) ? __('Edit role') : __('Create role') }}</h1> + </div> + <div class="col-sm-6"> + <ol class="breadcrumb float-sm-right"> + <li class="breadcrumb-item"><a href="{{ route('admin.settings.index') }}">{{ __('Dashboard') }}</a></li> + <li class="breadcrumb-item"><a href="{{ route('admin.roles.index') }}">{{ __('Roles List') }}</a></li> + <li class="breadcrumb-item"><a class="text-muted" + href="{{ isset($role) ? route('admin.roles.edit', $role->id) : route('admin.roles.create') }}">{{ isset($role) ? __('Edit role') : __('Create role') }}</a> + </li> + </ol> + </div> + </div> + </div> + </section> + + <section class="content"> + <div class="container-fluid"> + <div class="card"> + <div class="card-header"> + <div class="d-flex justify-content-between"> + <h5 class="card-title"><i class="mr-2 fas fa-user-check"></i>{{ isset($role) ? __('Edit role') : __('Create role') }}</h5> + </div> + </div> + <div class="card-body"> + <div class="p-0 col-12"> + <form method="post" action="{{isset($role) ? route('admin.roles.update', $role->id) : route('admin.roles.store')}}"> @csrf @isset($role) @@ -54,10 +78,12 @@ <button name="submit" type="submit" class="btn btn-primary">{{__('Submit')}}</button> </div> </form> - + </div> + </div> + </div> + </div> </div> - - </div> + </section> <script> document.addEventListener('DOMContentLoaded', (event) => { @@ -67,4 +93,3 @@ }) </script> @endsection - From 998876ba0372043e72b870dee9c981721efb6326 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Mon, 29 Jul 2024 10:14:46 -0600 Subject: [PATCH 456/514] fix: Ticket middleware --- routes/web.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/web.php b/routes/web.php index 444d9485b..7d56e5f04 100644 --- a/routes/web.php +++ b/routes/web.php @@ -119,9 +119,9 @@ Route::get('ticket', [TicketsController::class, 'index'])->name('ticket.index'); Route::get('ticket/datatable', [TicketsController::class, 'datatable'])->name('ticket.datatable'); Route::get('ticket/new', [TicketsController::class, 'create'])->name('ticket.new'); - Route::post('ticket/new', [TicketsController::class, 'store'])->middleware(['throttle:ticket-new'])->name('ticket.new.store'); + Route::post('ticket/new', [TicketsController::class, 'store'])->middleware(['throttle:1,1'])->name('ticket.new.store'); Route::get('ticket/show/{ticket_id}', [TicketsController::class, 'show'])->name('ticket.show'); - Route::post('ticket/reply', [TicketsController::class, 'reply'])->middleware(['throttle:ticket-reply'])->name('ticket.reply'); + Route::post('ticket/reply', [TicketsController::class, 'reply'])->middleware(['throttle:10,1'])->name('ticket.reply'); Route::post('ticket/status/{ticket_id}', [TicketsController::class, 'changeStatus'])->name('ticket.changeStatus'); From fca27fdf17b476c7721b38f91e62e83e78dd8be2 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Mon, 29 Jul 2024 11:11:28 -0600 Subject: [PATCH 457/514] feat: Update composer.lock --- composer.lock | 4267 +++++++++++++++++++++++++++---------------------- 1 file changed, 2318 insertions(+), 1949 deletions(-) diff --git a/composer.lock b/composer.lock index 3f224c0c4..20e494507 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8a9b4a3cda2a919fa33f41527b679dce", + "content-hash": "6284e796f9bb6fd440148a123c7a77e3", "packages": [ { "name": "aws/aws-crt-php", - "version": "v1.2.1", + "version": "v1.2.6", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "1926277fc71d253dfa820271ac5987bdb193ccf5" + "reference": "a63485b65b6b3367039306496d49737cf1995408" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/1926277fc71d253dfa820271ac5987bdb193ccf5", - "reference": "1926277fc71d253dfa820271ac5987bdb193ccf5", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408", + "reference": "a63485b65b6b3367039306496d49737cf1995408", "shasum": "" }, "require": { @@ -56,34 +56,35 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.1" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6" }, - "time": "2023-03-24T20:22:19+00:00" + "time": "2024-06-13T17:21:28+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.269.0", + "version": "3.316.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78" + "reference": "e832e594b3c213760e067e15ef2739f77505e832" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", - "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e832e594b3c213760e067e15ef2739f77505e832", + "reference": "e832e594b3c213760e067e15ef2739f77505e832", "shasum": "" }, "require": { - "aws/aws-crt-php": "^1.0.4", + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/promises": "^1.4.0 || ^2.0", "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "mtdowling/jmespath.php": "^2.6", - "php": ">=5.5" + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", @@ -98,9 +99,8 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", "psr/cache": "^1.0", - "psr/http-message": "^1.0", "psr/simple-cache": "^1.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" @@ -151,33 +151,33 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.269.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.316.3" }, - "time": "2023-04-26T18:21:04+00:00" + "time": "2024-07-12T18:07:23+00:00" }, { "name": "barryvdh/laravel-dompdf", - "version": "v2.0.1", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-dompdf.git", - "reference": "9843d2be423670fb434f4c978b3c0f4dd92c87a6" + "reference": "c96f90c97666cebec154ca1ffb67afed372114d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/9843d2be423670fb434f4c978b3c0f4dd92c87a6", - "reference": "9843d2be423670fb434f4c978b3c0f4dd92c87a6", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/c96f90c97666cebec154ca1ffb67afed372114d8", + "reference": "c96f90c97666cebec154ca1ffb67afed372114d8", "shasum": "" }, "require": { - "dompdf/dompdf": "^2.0.1", - "illuminate/support": "^6|^7|^8|^9|^10", + "dompdf/dompdf": "^2.0.7", + "illuminate/support": "^6|^7|^8|^9|^10|^11", "php": "^7.2 || ^8.0" }, "require-dev": { - "nunomaduro/larastan": "^1|^2", - "orchestra/testbench": "^4|^5|^6|^7|^8", - "phpro/grumphp": "^1", + "larastan/larastan": "^1.0|^2.7.0", + "orchestra/testbench": "^4|^5|^6|^7|^8|^9", + "phpro/grumphp": "^1 || ^2.5", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", @@ -218,7 +218,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-dompdf/issues", - "source": "https://github.com/barryvdh/laravel-dompdf/tree/v2.0.1" + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v2.2.0" }, "funding": [ { @@ -230,30 +230,30 @@ "type": "github" } ], - "time": "2023-01-12T15:12:49+00:00" + "time": "2024-04-25T13:16:04+00:00" }, { "name": "biscolab/laravel-recaptcha", - "version": "v5.4.0", + "version": "v6.1.0", "source": { "type": "git", "url": "https://github.com/biscolab/laravel-recaptcha.git", - "reference": "1bab726402d5376553a439b88a0faa07e84488fd" + "reference": "440fc617cba9f39aab7fda5d7697b76a55286e31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/biscolab/laravel-recaptcha/zipball/1bab726402d5376553a439b88a0faa07e84488fd", - "reference": "1bab726402d5376553a439b88a0faa07e84488fd", + "url": "https://api.github.com/repos/biscolab/laravel-recaptcha/zipball/440fc617cba9f39aab7fda5d7697b76a55286e31", + "reference": "440fc617cba9f39aab7fda5d7697b76a55286e31", "shasum": "" }, "require": { - "illuminate/routing": "^7.0|^8.0|^9.0", - "illuminate/support": "^7.0|^8.0|^9.0", + "illuminate/routing": "^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^7.0|^8.0|^9.0|^10.0|^11.0", "php": "^7.3|^8.0" }, "require-dev": { - "orchestra/testbench": "5.*|6.*|^7.0", - "phpunit/phpunit": "^9.1" + "orchestra/testbench": "5.*|6.*|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.1|^10.5" }, "suggest": { "biscolab/laravel-authlog": "It allows to handle logged-in users and force log-out if needed" @@ -299,31 +299,32 @@ ], "support": { "issues": "https://github.com/biscolab/laravel-recaptcha/issues", - "source": "https://github.com/biscolab/laravel-recaptcha/tree/v5.4.0" + "source": "https://github.com/biscolab/laravel-recaptcha/tree/v6.1.0" }, - "time": "2022-05-07T12:52:46+00:00" + "abandoned": true, + "time": "2024-03-27T11:06:21+00:00" }, { "name": "brick/math", - "version": "0.11.0", + "version": "0.12.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", - "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", "shasum": "" }, "require": { - "php": "^8.0" + "php": "^8.1" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^9.0", - "vimeo/psalm": "5.0.0" + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" }, "type": "library", "autoload": { @@ -343,12 +344,17 @@ "arithmetic", "bigdecimal", "bignum", + "bignumber", "brick", - "math" + "decimal", + "integer", + "math", + "mathematics", + "rational" ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.11.0" + "source": "https://github.com/brick/math/tree/0.12.1" }, "funding": [ { @@ -356,20 +362,89 @@ "type": "github" } ], - "time": "2023-01-15T23:15:59+00:00" + "time": "2023-11-29T23:19:16+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" }, { "name": "dflydev/dot-access-data", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "f41715465d65213d644d3141a6a93081be5d3549" + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", - "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { @@ -429,145 +504,48 @@ ], "support": { "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" - }, - "time": "2022-10-27T11:44:00+00:00" - }, - { - "name": "doctrine/cache", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.0" + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2022-05-20T20:07:39+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { "name": "doctrine/dbal", - "version": "3.6.2", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "b4bd1cfbd2b916951696d82e57d054394d84864c" + "reference": "50fda19f80724b55ff770bb4ff352407008e63c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/b4bd1cfbd2b916951696d82e57d054394d84864c", - "reference": "b4bd1cfbd2b916951696d82e57d054394d84864c", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/50fda19f80724b55ff770bb4ff352407008e63c5", + "reference": "50fda19f80724b55ff770bb4ff352407008e63c5", "shasum": "" }, "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", + "php": "^8.1", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, "require-dev": { - "doctrine/coding-standard": "11.1.0", + "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.10.9", - "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.6", - "psalm/plugin-phpunit": "0.18.4", - "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^5.4|^6.0", - "symfony/console": "^4.4|^5.4|^6.0", - "vimeo/psalm": "4.30.0" + "jetbrains/phpstorm-stubs": "2023.2", + "phpstan/phpstan": "1.11.5", + "phpstan/phpstan-phpunit": "1.4.0", + "phpstan/phpstan-strict-rules": "^1.6", + "phpunit/phpunit": "10.5.22", + "psalm/plugin-phpunit": "0.19.0", + "slevomat/coding-standard": "8.13.1", + "squizlabs/php_codesniffer": "3.10.1", + "symfony/cache": "^6.3.8|^7.0", + "symfony/console": "^5.4|^6.3|^7.0", + "vimeo/psalm": "5.24.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": [ - "bin/doctrine-dbal" - ], "type": "library", "autoload": { "psr-4": { @@ -620,7 +598,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.2" + "source": "https://github.com/doctrine/dbal/tree/4.0.4" }, "funding": [ { @@ -636,29 +614,33 @@ "type": "tidelift" } ], - "time": "2023-04-14T07:25:38+00:00" + "time": "2024-06-19T11:57:23+00:00" }, { "name": "doctrine/deprecations", - "version": "v1.0.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", "shasum": "" }, "require": { - "php": "^7.1|^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5|^8.5|^9.5", - "psr/log": "^1|^2|^3" + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -677,125 +659,34 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" - }, - "time": "2022-05-02T15:47:09+00:00" - }, - { - "name": "doctrine/event-manager", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "conflict": { - "doctrine/common": "<2.9" - }, - "require-dev": { - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.28" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.0" + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2022-10-12T20:59:15+00:00" + "time": "2024-01-30T19:34:25+00:00" }, { "name": "doctrine/inflector", - "version": "2.0.6", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024" + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", - "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^10", + "doctrine/coding-standard": "^11.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25" + "vimeo/psalm": "^4.25 || ^5.4" }, "type": "library", "autoload": { @@ -845,7 +736,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.6" + "source": "https://github.com/doctrine/inflector/tree/2.0.10" }, "funding": [ { @@ -861,31 +752,31 @@ "type": "tidelift" } ], - "time": "2022-10-20T09:10:12+00:00" + "time": "2024-02-18T20:23:39+00:00" }, { "name": "doctrine/lexer", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "84a527db05647743d50373e0ec53a152f2cde568" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568", - "reference": "84a527db05647743d50373e0ec53a152f2cde568", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.5", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.0" + "vimeo/psalm": "^5.21" }, "type": "library", "autoload": { @@ -922,7 +813,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.0" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -938,20 +829,20 @@ "type": "tidelift" } ], - "time": "2022-12-15T16:57:16+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "dompdf/dompdf", - "version": "v2.0.3", + "version": "v2.0.8", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "e8d2d5e37e8b0b30f0732a011295ab80680d7e85" + "reference": "c20247574601700e1f7c8dab39310fca1964dc52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/e8d2d5e37e8b0b30f0732a011295ab80680d7e85", - "reference": "e8d2d5e37e8b0b30f0732a011295ab80680d7e85", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/c20247574601700e1f7c8dab39310fca1964dc52", + "reference": "c20247574601700e1f7c8dab39310fca1964dc52", "shasum": "" }, "require": { @@ -959,7 +850,7 @@ "ext-mbstring": "*", "masterminds/html5": "^2.0", "phenx/php-font-lib": ">=0.5.4 <1.0.0", - "phenx/php-svg-lib": ">=0.3.3 <1.0.0", + "phenx/php-svg-lib": ">=0.5.2 <1.0.0", "php": "^7.1 || ^8.0" }, "require-dev": { @@ -998,22 +889,22 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v2.0.3" + "source": "https://github.com/dompdf/dompdf/tree/v2.0.8" }, - "time": "2023-02-07T12:51:48+00:00" + "time": "2024-04-29T13:06:17+00:00" }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.2", + "version": "v3.3.3", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", - "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", "shasum": "" }, "require": { @@ -1053,7 +944,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" }, "funding": [ { @@ -1061,20 +952,20 @@ "type": "github" } ], - "time": "2022-09-10T18:51:20+00:00" + "time": "2023-08-10T19:36:49+00:00" }, { "name": "egulias/email-validator", - "version": "4.0.1", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", "shasum": "" }, "require": { @@ -1083,8 +974,8 @@ "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^4.30" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -1120,7 +1011,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" }, "funding": [ { @@ -1128,7 +1019,7 @@ "type": "github" } ], - "time": "2023-01-14T14:17:03+00:00" + "time": "2023-10-06T06:47:41+00:00" }, { "name": "facade/ignition-contracts", @@ -1183,23 +1074,86 @@ }, "time": "2020-10-16T08:27:54+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v6.10.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "500501c2ce893c824c801da135d02661199f60c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5", + "reference": "500501c2ce893c824c801da135d02661199f60c5", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.10.1" + }, + "time": "2024-05-18T18:05:11+00:00" + }, { "name": "fruitcake/php-cors", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/fruitcake/php-cors.git", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", - "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "symfony/http-foundation": "^4.4|^5.4|^6" + "symfony/http-foundation": "^4.4|^5.4|^6|^7" }, "require-dev": { "phpstan/phpstan": "^1.4", @@ -1209,7 +1163,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.1-dev" + "dev-master": "1.2-dev" } }, "autoload": { @@ -1240,7 +1194,7 @@ ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", - "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" }, "funding": [ { @@ -1252,28 +1206,28 @@ "type": "github" } ], - "time": "2022-02-20T15:07:15+00:00" + "time": "2023-10-12T05:21:21+00:00" }, { "name": "graham-campbell/result-type", - "version": "v1.1.1", + "version": "v1.1.3", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", - "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.1" + "phpoption/phpoption": "^1.9.3" }, "require-dev": { - "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "autoload": { @@ -1302,7 +1256,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" }, "funding": [ { @@ -1314,26 +1268,26 @@ "type": "tidelift" } ], - "time": "2023-02-25T20:23:15+00:00" + "time": "2024-07-20T21:45:45+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.5.1", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9", - "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1342,10 +1296,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1358,9 +1313,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "7.5-dev" } }, "autoload": { @@ -1426,7 +1378,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -1442,38 +1394,37 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:30:08+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "b94b2807d85443f9719887892882d0329d1e2598" + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", - "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.5-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -1510,7 +1461,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.2" + "source": "https://github.com/guzzle/promises/tree/2.0.3" }, "funding": [ { @@ -1526,20 +1477,20 @@ "type": "tidelift" } ], - "time": "2022-08-28T14:55:35+00:00" + "time": "2024-07-18T10:29:17+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.5.0", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -1553,9 +1504,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1626,7 +1577,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.5.0" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -1642,34 +1593,36 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:11:26+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.1", + "version": "v1.0.3", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2" + "reference": "ecea8feef63bd4fef1f037ecb288386999ecc11c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/b945d74a55a25a949158444f09ec0d3c120d69e2", - "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/ecea8feef63bd4fef1f037ecb288386999ecc11c", + "reference": "ecea8feef63bd4fef1f037ecb288386999ecc11c", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "symfony/polyfill-php80": "^1.17" + "symfony/polyfill-php80": "^1.24" }, "require-dev": { - "phpunit/phpunit": "^8.5.19 || ^9.5.8", + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "uri-template/tests": "1.0.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { @@ -1710,7 +1663,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.1" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.3" }, "funding": [ { @@ -1726,7 +1679,7 @@ "type": "tidelift" } ], - "time": "2021-10-07T12:57:01+00:00" + "time": "2023-12-03T19:50:20+00:00" }, { "name": "hidehalo/nanoid-php", @@ -1787,29 +1740,29 @@ }, { "name": "kkomelin/laravel-translatable-string-exporter", - "version": "1.21.0", + "version": "1.22.0", "source": { "type": "git", "url": "https://github.com/kkomelin/laravel-translatable-string-exporter.git", - "reference": "51e6575223c345be359f5387ecaaf6bb7fc1de3d" + "reference": "0c6dbec4694a7e702830ecfc005d131cd5ffe402" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kkomelin/laravel-translatable-string-exporter/zipball/51e6575223c345be359f5387ecaaf6bb7fc1de3d", - "reference": "51e6575223c345be359f5387ecaaf6bb7fc1de3d", + "url": "https://api.github.com/repos/kkomelin/laravel-translatable-string-exporter/zipball/0c6dbec4694a7e702830ecfc005d131cd5ffe402", + "reference": "0c6dbec4694a7e702830ecfc005d131cd5ffe402", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/support": "^8|^9|^10.0", - "illuminate/translation": "^8|^9|^10.0", + "illuminate/support": "^8|^9|^10.0|^11.0", + "illuminate/translation": "^8|^9|^10.0|^11.0", "php": "^8.0", - "symfony/finder": "^5|^6" + "symfony/finder": "^5|^6|^7.0" }, "require-dev": { "nunomaduro/larastan": "^1.0|^2.0", - "orchestra/testbench": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^9.0" + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.0|^10.5" }, "type": "library", "extra": { @@ -1846,26 +1799,27 @@ ], "support": { "issues": "https://github.com/kkomelin/laravel-translatable-string-exporter/issues", - "source": "https://github.com/kkomelin/laravel-translatable-string-exporter/tree/1.21.0" + "source": "https://github.com/kkomelin/laravel-translatable-string-exporter/tree/1.22.0" }, - "time": "2023-03-14T04:18:49+00:00" + "time": "2024-03-13T13:44:41+00:00" }, { "name": "laravel/framework", - "version": "v9.52.7", + "version": "v11.18.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "675ea868fe36b18c8303e954aac540e6b1caa677" + "reference": "b19ba518c56852567e99fbae9321bc436c2cc5a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/675ea868fe36b18c8303e954aac540e6b1caa677", - "reference": "675ea868fe36b18c8303e954aac540e6b1caa677", + "url": "https://api.github.com/repos/laravel/framework/zipball/b19ba518c56852567e99fbae9321bc436c2cc5a8", + "reference": "b19ba518c56852567e99fbae9321bc436c2cc5a8", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", "egulias/email-validator": "^3.2.1|^4.0", @@ -1876,39 +1830,44 @@ "ext-openssl": "*", "ext-session": "*", "ext-tokenizer": "*", - "fruitcake/php-cors": "^1.2", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8", "guzzlehttp/uri-template": "^1.0", - "laravel/serializable-closure": "^1.2.2", + "laravel/prompts": "^0.1.18", + "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", - "monolog/monolog": "^2.0", - "nesbot/carbon": "^2.62.1", - "nunomaduro/termwind": "^1.13", - "php": "^8.0.2", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^2.72.2|^3.0", + "nunomaduro/termwind": "^2.0", + "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^6.0.9", - "symfony/error-handler": "^6.0", - "symfony/finder": "^6.0", - "symfony/http-foundation": "^6.0", - "symfony/http-kernel": "^6.0", - "symfony/mailer": "^6.0", - "symfony/mime": "^6.0", - "symfony/process": "^6.0", - "symfony/routing": "^6.0", - "symfony/uid": "^6.0", - "symfony/var-dumper": "^6.0", + "symfony/console": "^7.0", + "symfony/error-handler": "^7.0", + "symfony/finder": "^7.0", + "symfony/http-foundation": "^7.0", + "symfony/http-kernel": "^7.0", + "symfony/mailer": "^7.0", + "symfony/mime": "^7.0", + "symfony/polyfill-php83": "^1.28", + "symfony/process": "^7.0", + "symfony/routing": "^7.0", + "symfony/uid": "^7.0", + "symfony/var-dumper": "^7.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.4.1", "voku/portable-ascii": "^2.0" }, "conflict": { + "mockery/mockery": "1.6.8", "tightenco/collect": "<5.5.33" }, "provide": { "psr/container-implementation": "1.1|2.0", + "psr/log-implementation": "1.0|2.0|3.0", "psr/simple-cache-implementation": "1.0|2.0|3.0" }, "replace": { @@ -1935,6 +1894,7 @@ "illuminate/notifications": "self.version", "illuminate/pagination": "self.version", "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", "illuminate/queue": "self.version", "illuminate/redis": "self.version", "illuminate/routing": "self.version", @@ -1943,35 +1903,35 @@ "illuminate/testing": "self.version", "illuminate/translation": "self.version", "illuminate/validation": "self.version", - "illuminate/view": "self.version" + "illuminate/view": "self.version", + "spatie/once": "*" }, "require-dev": { "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.235.5", - "doctrine/dbal": "^2.13.3|^3.1.4", "ext-gmp": "*", - "fakerphp/faker": "^1.21", - "guzzlehttp/guzzle": "^7.5", + "fakerphp/faker": "^1.23", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-ftp": "^3.0", "league/flysystem-path-prefixing": "^3.3", "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", - "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^7.24", - "pda/pheanstalk": "^4.0", - "phpstan/phpdoc-parser": "^1.15", - "phpstan/phpstan": "^1.4.7", - "phpunit/phpunit": "^9.5.8", - "predis/predis": "^1.1.9|^2.0.2", - "symfony/cache": "^6.0", - "symfony/http-client": "^6.0" + "mockery/mockery": "^1.6", + "nyholm/psr7": "^1.2", + "orchestra/testbench-core": "^9.1.5", + "pda/pheanstalk": "^5.0", + "phpstan/phpstan": "^1.11.5", + "phpunit/phpunit": "^10.5|^11.0", + "predis/predis": "^2.0.2", + "resend/resend-php": "^0.10.0", + "symfony/cache": "^7.0", + "symfony/http-client": "^7.0", + "symfony/psr-http-message-bridge": "^7.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", - "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", "ext-apcu": "Required to use the APC cache driver.", "ext-fileinfo": "Required to use the Filesystem class.", "ext-ftp": "Required to use the Flysystem FTP driver.", @@ -1980,40 +1940,41 @@ "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", - "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", - "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", "league/flysystem-read-only": "Required to use read-only disks (^3.3)", "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", - "mockery/mockery": "Required to use mocking (^1.5.1).", + "mockery/mockery": "Required to use mocking (^1.6).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", - "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8).", - "predis/predis": "Required to use the predis connector (^1.1.9|^2.0.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).", + "predis/predis": "Required to use the predis connector (^2.0.2).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^6.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^6.0).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.0).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.x-dev" + "dev-master": "11.x-dev" } }, "autoload": { "files": [ "src/Illuminate/Collections/helpers.php", "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", "src/Illuminate/Support/helpers.php" ], @@ -2046,20 +2007,78 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-04-25T13:44:05+00:00" + "time": "2024-07-26T10:39:29+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.1.24", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "409b0b4305273472f3754826e68f4edbd0150149" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/409b0b4305273472f3754826e68f4edbd0150149", + "reference": "409b0b4305273472f3754826e68f4edbd0150149", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/collections": "^10.0|^11.0", + "php": "^8.1", + "symfony/console": "^6.2|^7.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-mockery": "^1.1" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.1.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.1.24" + }, + "time": "2024-06-17T13:58:22+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.0", + "version": "v1.3.3", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37" + "reference": "3dbf8a8e914634c48d389c1234552666b3d43754" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", + "reference": "3dbf8a8e914634c48d389c1234552666b3d43754", "shasum": "" }, "require": { @@ -2106,35 +2125,38 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-01-30T18:31:20+00:00" + "time": "2023-11-08T14:08:06+00:00" }, { "name": "laravel/socialite", - "version": "v5.6.1", + "version": "v5.15.1", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "a14a177f2cc71d8add71e2b19e00800e83bdda09" + "reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/a14a177f2cc71d8add71e2b19e00800e83bdda09", - "reference": "a14a177f2cc71d8add71e2b19e00800e83bdda09", + "url": "https://api.github.com/repos/laravel/socialite/zipball/cc02625f0bd1f95dc3688eb041cce0f1e709d029", + "reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029", "shasum": "" }, "require": { "ext-json": "*", + "firebase/php-jwt": "^6.4", "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "league/oauth1-client": "^1.10.1", - "php": "^7.2|^8.0" + "php": "^7.2|^8.0", + "phpseclib/phpseclib": "^3.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0", - "phpunit/phpunit": "^8.0|^9.3" + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.0|^9.3|^10.4" }, "type": "library", "extra": { @@ -2175,42 +2197,40 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2023-01-20T15:42:35+00:00" + "time": "2024-06-28T20:09:34+00:00" }, { "name": "laravel/tinker", - "version": "v2.8.1", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10" + "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", - "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", + "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "php": "^7.2.5|^8.0", - "psy/psysh": "^0.10.4|^0.11.1", - "symfony/var-dumper": "^4.3.4|^5.0|^6.0" + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0)." + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0)." }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - }, "laravel": { "providers": [ "Laravel\\Tinker\\TinkerServiceProvider" @@ -2241,38 +2261,40 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.8.1" + "source": "https://github.com/laravel/tinker/tree/v2.9.0" }, - "time": "2023-02-15T16:40:09+00:00" + "time": "2024-01-04T16:10:04+00:00" }, { "name": "laravel/ui", - "version": "v3.4.6", + "version": "v4.5.2", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c" + "reference": "c75396f63268c95b053c8e4814eb70e0875e9628" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c", - "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c", + "url": "https://api.github.com/repos/laravel/ui/zipball/c75396f63268c95b053c8e4814eb70e0875e9628", + "reference": "c75396f63268c95b053c8e4814eb70e0875e9628", "shasum": "" }, "require": { - "illuminate/console": "^8.42|^9.0", - "illuminate/filesystem": "^8.42|^9.0", - "illuminate/support": "^8.82|^9.0", - "illuminate/validation": "^8.42|^9.0", - "php": "^7.3|^8.0" + "illuminate/console": "^9.21|^10.0|^11.0", + "illuminate/filesystem": "^9.21|^10.0|^11.0", + "illuminate/support": "^9.21|^10.0|^11.0", + "illuminate/validation": "^9.21|^10.0|^11.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0" }, "require-dev": { - "orchestra/testbench": "^6.23|^7.0" + "orchestra/testbench": "^7.35|^8.15|^9.0", + "phpunit/phpunit": "^9.3|^10.4|^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "4.x-dev" }, "laravel": { "providers": [ @@ -2302,34 +2324,33 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v3.4.6" + "source": "https://github.com/laravel/ui/tree/v4.5.2" }, - "time": "2022-05-20T13:38:08+00:00" + "time": "2024-05-08T18:07:10+00:00" }, { "name": "laraveldaily/laravel-invoices", - "version": "3.1.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/LaravelDaily/laravel-invoices.git", - "reference": "7f3f2fd2042ad7ca228b506059a1e8535de46e05" + "reference": "d9fa7eca22a7836fb9b09ee7fc2a97b4dfd7b228" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LaravelDaily/laravel-invoices/zipball/7f3f2fd2042ad7ca228b506059a1e8535de46e05", - "reference": "7f3f2fd2042ad7ca228b506059a1e8535de46e05", + "url": "https://api.github.com/repos/LaravelDaily/laravel-invoices/zipball/d9fa7eca22a7836fb9b09ee7fc2a97b4dfd7b228", + "reference": "d9fa7eca22a7836fb9b09ee7fc2a97b4dfd7b228", "shasum": "" }, "require": { "barryvdh/laravel-dompdf": "^v2.0", - "illuminate/http": "^5.5|^6|^7|^8|^9|^10", - "illuminate/support": "^5.5|^6|^7|^8|^9|^10", - "php": "^7.3|^8.0", - "symfony/http-foundation": "^4.0|^5.0|^6.0" + "illuminate/http": "^10|^11", + "illuminate/support": "^10|^11", + "php": ">=8.2", + "symfony/http-foundation": "^6|^7" }, "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/var-dumper": "^5.0" + "phpunit/phpunit": "^10.1" }, "type": "library", "extra": { @@ -2369,22 +2390,22 @@ ], "support": { "issues": "https://github.com/LaravelDaily/laravel-invoices/issues", - "source": "https://github.com/LaravelDaily/laravel-invoices/tree/3.1.0" + "source": "https://github.com/LaravelDaily/laravel-invoices/tree/4.0.0" }, - "time": "2023-02-15T12:35:38+00:00" + "time": "2024-03-14T08:31:01+00:00" }, { "name": "league/commonmark", - "version": "2.4.0", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048" + "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d44a24690f16b8c1808bf13b1bd54ae4c63ea048", - "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ac815920de0eff6de947eac0a6a94e5ed0fb147c", + "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c", "shasum": "" }, "require": { @@ -2397,8 +2418,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.0", + "commonmark/commonmark.js": "0.31.0", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -2407,10 +2428,10 @@ "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "symfony/finder": "^5.3 | ^6.0 || ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, @@ -2420,7 +2441,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "2.6-dev" } }, "autoload": { @@ -2477,7 +2498,7 @@ "type": "tidelift" } ], - "time": "2023-03-24T15:16:10+00:00" + "time": "2024-07-24T12:52:09+00:00" }, { "name": "league/config", @@ -2563,23 +2584,26 @@ }, { "name": "league/flysystem", - "version": "3.14.0", + "version": "3.28.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "e2a279d7f47d9098e479e8b21f7fb8b8de230158" + "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e2a279d7f47d9098e479e8b21f7fb8b8de230158", - "reference": "e2a279d7f47d9098e479e8b21f7fb8b8de230158", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", "shasum": "" }, "require": { + "league/flysystem-local": "^3.0.0", "league/mime-type-detection": "^1.0.0", "php": "^8.0.2" }, "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", "aws/aws-sdk-php": "3.209.31 || 3.210.0", "guzzlehttp/guzzle": "<7.0", "guzzlehttp/ringphp": "<1.1.1", @@ -2587,20 +2611,23 @@ "symfony/http-client": "<5.2" }, "require-dev": { - "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.1", - "aws/aws-sdk-php": "^3.220.0", + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", + "ext-mongodb": "^1.3", "ext-zip": "*", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", "microsoft/azure-storage-blob": "^1.1", - "phpseclib/phpseclib": "^3.0.14", - "phpstan/phpstan": "^0.12.26", - "phpunit/phpunit": "^9.5.11", - "sabre/dav": "^4.3.1" + "mongodb/mongodb": "^1.2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" }, "type": "library", "autoload": { @@ -2634,48 +2661,89 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.14.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.28.0" }, - "funding": [ - { - "url": "https://ecologi.com/frankdejonge", - "type": "custom" - }, + "time": "2024-05-22T10:09:12+00:00" + }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "3.28.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "22071ef1604bc776f5ff2468ac27a752514665c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/22071ef1604bc776f5ff2468ac27a752514665c8", + "reference": "22071ef1604bc776f5ff2468ac27a752514665c8", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.295.10", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "url": "https://github.com/frankdejonge", - "type": "github" + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" } ], - "time": "2023-04-11T18:11:47+00:00" + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.28.0" + }, + "time": "2024-05-06T20:05:52+00:00" }, { - "name": "league/flysystem-aws-s3-v3", - "version": "3.13.0", + "name": "league/flysystem-local", + "version": "3.28.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "8e04cbb403d4dfd5b73a2f8685f1df395bd177eb" + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/8e04cbb403d4dfd5b73a2f8685f1df395bd177eb", - "reference": "8e04cbb403d4dfd5b73a2f8685f1df395bd177eb", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40", "shasum": "" }, "require": { - "aws/aws-sdk-php": "^3.220.0", - "league/flysystem": "^3.10.0", + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", "league/mime-type-detection": "^1.0.0", "php": "^8.0.2" }, - "conflict": { - "guzzlehttp/guzzle": "<7.0", - "guzzlehttp/ringphp": "<1.1.1" - }, "type": "library", "autoload": { "psr-4": { - "League\\Flysystem\\AwsS3V3\\": "" + "League\\Flysystem\\Local\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2688,54 +2756,41 @@ "email": "info@frankdejonge.nl" } ], - "description": "AWS S3 filesystem adapter for Flysystem.", + "description": "Local filesystem adapter for Flysystem.", "keywords": [ "Flysystem", - "aws", "file", "files", "filesystem", - "s3", - "storage" + "local" ], "support": { - "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.13.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0" }, - "funding": [ - { - "url": "https://ecologi.com/frankdejonge", - "type": "custom" - }, - { - "url": "https://github.com/frankdejonge", - "type": "github" - } - ], - "time": "2023-03-16T14:29:01+00:00" + "time": "2024-05-06T20:05:52+00:00" }, { "name": "league/mime-type-detection", - "version": "1.11.0", + "version": "1.15.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", "shasum": "" }, "require": { "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", "autoload": { @@ -2756,7 +2811,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" }, "funding": [ { @@ -2768,7 +2823,7 @@ "type": "tidelift" } ], - "time": "2022-04-17T13:12:02+00:00" + "time": "2024-01-28T23:22:08+00:00" }, { "name": "league/oauth1-client", @@ -2848,16 +2903,16 @@ }, { "name": "masterminds/html5", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3" + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3", - "reference": "3c5d5a56d56f48a1ca08a0670f0f80c1dad368f3", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", "shasum": "" }, "require": { @@ -2865,7 +2920,7 @@ "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8" + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" }, "type": "library", "extra": { @@ -2909,48 +2964,47 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.8.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" }, - "time": "2023-04-26T07:27:39+00:00" + "time": "2024-03-31T07:05:07+00:00" }, { "name": "monolog/monolog", - "version": "2.9.1", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1" + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1", - "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", "shasum": "" }, "require": { - "php": ">=7.2", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + "psr/log-implementation": "3.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "aws/aws-sdk-php": "^3.0", "doctrine/couchdb": "~1.0@dev", "elasticsearch/elasticsearch": "^7 || ^8", "ext-json": "*", - "graylog2/gelf-php": "^1.4.2 || ^2@dev", - "guzzlehttp/guzzle": "^7.4", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpspec/prophecy": "^1.15", - "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5.14", - "predis/predis": "^1.1 || ^2.0", - "rollbar/rollbar": "^1.3 || ^2 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.5.17", + "predis/predis": "^1.1 || ^2", "ruflin/elastica": "^7", - "swiftmailer/swiftmailer": "^5.3|^6.0", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -2973,7 +3027,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -3001,7 +3055,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.9.1" + "source": "https://github.com/Seldaek/monolog/tree/3.7.0" }, "funding": [ { @@ -3013,29 +3067,29 @@ "type": "tidelift" } ], - "time": "2023-02-06T13:44:46+00:00" + "time": "2024-06-28T09:40:51+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.6.1", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0", + "php": "^7.2.5 || ^8.0", "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "composer/xdebug-handler": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^7.5.15" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, "bin": [ "bin/jp.php" @@ -3043,7 +3097,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.7-dev" } }, "autoload": { @@ -3059,6 +3113,11 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", @@ -3072,43 +3131,47 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" }, - "time": "2021-06-14T00:11:39+00:00" + "time": "2023-08-25T10:54:48+00:00" }, { "name": "nesbot/carbon", - "version": "2.66.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "496712849902241f04902033b0441b269effe001" + "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/496712849902241f04902033b0441b269effe001", - "reference": "496712849902241f04902033b0441b269effe001", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cb4374784c87d0a0294e8513a52eb63c0aff3139", + "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139", "shasum": "" }, "require": { + "carbonphp/carbon-doctrine-types": "*", "ext-json": "*", - "php": "^7.1.8 || ^8.0", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3 || ^7.0", "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" + }, + "provide": { + "psr/clock-implementation": "1.0" }, "require-dev": { - "doctrine/dbal": "^2.0 || ^3.1.4", - "doctrine/orm": "^2.7", - "friendsofphp/php-cs-fixer": "^3.0", - "kylekatarnls/multi-tester": "^2.0", - "ondrejmirtes/better-reflection": "*", - "phpmd/phpmd": "^2.9", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.99 || ^1.7.14", - "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", - "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", - "squizlabs/php_codesniffer": "^3.4" + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.57.2", + "kylekatarnls/multi-tester": "^2.5.3", + "ondrejmirtes/better-reflection": "^6.25.0.4", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.11.2", + "phpunit/phpunit": "^10.5.20", + "squizlabs/php_codesniffer": "^3.9.0" }, "bin": [ "bin/carbon" @@ -3116,8 +3179,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-3.x": "3.x-dev", - "dev-master": "2.x-dev" + "dev-master": "3.x-dev", + "dev-2.x": "2.x-dev" }, "laravel": { "providers": [ @@ -3176,35 +3239,35 @@ "type": "tidelift" } ], - "time": "2023-01-29T18:53:47+00:00" + "time": "2024-07-16T22:29:20+00:00" }, { "name": "nette/schema", - "version": "v1.2.3", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", "shasum": "" }, "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.3" + "nette/utils": "^4.0", + "php": "8.1 - 8.3" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", + "nette/tester": "^2.4", "phpstan/phpstan-nette": "^1.0", - "tracy/tracy": "^2.7" + "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -3236,26 +3299,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.3" + "source": "https://github.com/nette/schema/tree/v1.3.0" }, - "time": "2022-10-13T01:24:26+00:00" + "time": "2023-12-11T11:54:22+00:00" }, { "name": "nette/utils", - "version": "v4.0.0", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", - "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", + "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", "shasum": "" }, "require": { - "php": ">=8.0 <8.3" + "php": ">=8.0 <8.4" }, "conflict": { "nette/finder": "<3", @@ -3263,7 +3326,7 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "dev-master", - "nette/tester": "^2.4", + "nette/tester": "^2.5", "phpstan/phpstan": "^1.0", "tracy/tracy": "^2.9" }, @@ -3273,8 +3336,7 @@ "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", - "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", - "ext-xml": "to use Strings::length() etc. when mbstring is not available" + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" }, "type": "library", "extra": { @@ -3323,31 +3385,33 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.0" + "source": "https://github.com/nette/utils/tree/v4.0.4" }, - "time": "2023-02-02T10:41:53+00:00" + "time": "2024-01-17T16:50:36+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.4", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -3355,7 +3419,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3379,39 +3443,38 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2023-03-05T19:49:14+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "nunomaduro/termwind", - "version": "v1.15.1", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc" + "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/8ab0b32c8caa4a2e09700ea32925441385e4a5dc", - "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/58c4c58cf23df7f498daeb97092e34f5259feb6a", + "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": "^8.0", - "symfony/console": "^5.3.0|^6.0.0" + "php": "^8.2", + "symfony/console": "^7.0.4" }, "require-dev": { - "ergebnis/phpstan-rules": "^1.0.", - "illuminate/console": "^8.0|^9.0", - "illuminate/support": "^8.0|^9.0", - "laravel/pint": "^1.0.0", - "pestphp/pest": "^1.21.0", - "pestphp/pest-plugin-mock": "^1.0", - "phpstan/phpstan": "^1.4.6", - "phpstan/phpstan-strict-rules": "^1.1.0", - "symfony/var-dumper": "^5.2.7|^6.0.0", + "ergebnis/phpstan-rules": "^2.2.0", + "illuminate/console": "^11.0.0", + "laravel/pint": "^1.14.0", + "mockery/mockery": "^1.6.7", + "pestphp/pest": "^2.34.1", + "phpstan/phpstan": "^1.10.59", + "phpstan/phpstan-strict-rules": "^1.5.2", + "symfony/var-dumper": "^7.0.4", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -3420,6 +3483,9 @@ "providers": [ "Termwind\\Laravel\\TermwindServiceProvider" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" } }, "autoload": { @@ -3451,7 +3517,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.15.1" + "source": "https://github.com/nunomaduro/termwind/tree/v2.0.1" }, "funding": [ { @@ -3467,7 +3533,74 @@ "type": "github" } ], - "time": "2023-02-08T01:06:31+00:00" + "time": "2024-03-06T16:17:14+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2024-05-08T12:36:18+00:00" }, { "name": "paragonie/random_compat", @@ -3615,79 +3748,25 @@ }, "time": "2021-09-14T21:35:26+00:00" }, - { - "name": "paypal/rest-api-sdk-php", - "version": "1.14.0", - "source": { - "type": "git", - "url": "https://github.com/paypal/PayPal-PHP-SDK.git", - "reference": "72e2f2466975bf128a31e02b15110180f059fc04" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paypal/PayPal-PHP-SDK/zipball/72e2f2466975bf128a31e02b15110180f059fc04", - "reference": "72e2f2466975bf128a31e02b15110180f059fc04", - "shasum": "" - }, - "require": { - "ext-curl": "*", - "ext-json": "*", - "php": ">=5.3.0", - "psr/log": "^1.0.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "type": "library", - "autoload": { - "psr-0": { - "PayPal": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "PayPal", - "homepage": "https://github.com/paypal/rest-api-sdk-php/contributors" - } - ], - "description": "PayPal's PHP SDK for REST APIs", - "homepage": "http://paypal.github.io/PayPal-PHP-SDK/", - "keywords": [ - "payments", - "paypal", - "rest", - "sdk" - ], - "support": { - "issues": "https://github.com/paypal/PayPal-PHP-SDK/issues", - "source": "https://github.com/paypal/PayPal-PHP-SDK/tree/master" - }, - "abandoned": true, - "time": "2019-01-04T20:04:25+00:00" - }, { "name": "phenx/php-font-lib", - "version": "0.5.4", + "version": "0.5.6", "source": { "type": "git", "url": "https://github.com/dompdf/php-font-lib.git", - "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4" + "reference": "a1681e9793040740a405ac5b189275059e2a9863" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/dd448ad1ce34c63d09baccd05415e361300c35b4", - "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a1681e9793040740a405ac5b189275059e2a9863", + "reference": "a1681e9793040740a405ac5b189275059e2a9863", "shasum": "" }, "require": { "ext-mbstring": "*" }, "require-dev": { - "symfony/phpunit-bridge": "^3 || ^4 || ^5" + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" }, "type": "library", "autoload": { @@ -3697,7 +3776,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "LGPL-2.1-or-later" ], "authors": [ { @@ -3709,22 +3788,22 @@ "homepage": "https://github.com/PhenX/php-font-lib", "support": { "issues": "https://github.com/dompdf/php-font-lib/issues", - "source": "https://github.com/dompdf/php-font-lib/tree/0.5.4" + "source": "https://github.com/dompdf/php-font-lib/tree/0.5.6" }, - "time": "2021-12-17T19:44:54+00:00" + "time": "2024-01-29T14:45:26+00:00" }, { "name": "phenx/php-svg-lib", - "version": "0.5.0", + "version": "0.5.4", "source": { "type": "git", "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "76876c6cf3080bcb6f249d7d59705108166a6685" + "reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/76876c6cf3080bcb6f249d7d59705108166a6685", - "reference": "76876c6cf3080bcb6f249d7d59705108166a6685", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/46b25da81613a9cf43c83b2a8c2c1bdab27df691", + "reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691", "shasum": "" }, "require": { @@ -3743,7 +3822,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "LGPL-3.0-or-later" ], "authors": [ { @@ -3755,9 +3834,9 @@ "homepage": "https://github.com/PhenX/php-svg-lib", "support": { "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.0" + "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.4" }, - "time": "2022-09-06T12:16:56+00:00" + "time": "2024-04-08T12:52:34+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -3814,21 +3893,21 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.7.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "dfc078e8af9c99210337325ff5aa152872c98714" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714", - "reference": "dfc078e8af9c99210337325ff5aa152872c98714", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", "phpstan/phpdoc-parser": "^1.13" }, @@ -3866,22 +3945,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2023-03-27T19:02:04+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.1", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", - "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", "shasum": "" }, "require": { @@ -3889,13 +3968,13 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "1.9-dev" @@ -3931,7 +4010,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" }, "funding": [ { @@ -3943,26 +4022,138 @@ "type": "tidelift" } ], - "time": "2023-02-25T19:38:58+00:00" + "time": "2024-07-20T21:41:07+00:00" + }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.39", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/211ebc399c6e73c225a018435fe5ae209d1d1485", + "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2|^3", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.39" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2024-06-24T06:27:33+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.20.3", + "version": "1.29.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2" + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6c04009f6cae6eda2f040745b6b846080ef069c2", - "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.5", @@ -3986,22 +4177,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.3" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" }, - "time": "2023-04-25T09:01:03+00:00" + "time": "2024-05-31T08:52:43+00:00" }, { "name": "predis/predis", - "version": "v2.1.2", + "version": "v2.2.2", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "a77a43913a74f9331f637bb12867eb8e274814e5" + "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/a77a43913a74f9331f637bb12867eb8e274814e5", - "reference": "a77a43913a74f9331f637bb12867eb8e274814e5", + "url": "https://api.github.com/repos/predis/predis/zipball/b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", + "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", "shasum": "" }, "require": { @@ -4012,6 +4203,9 @@ "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^8.0 || ~9.4.4" }, + "suggest": { + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" + }, "type": "library", "autoload": { "psr-4": { @@ -4037,43 +4231,87 @@ "redis" ], "support": { - "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.1.2" + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v2.2.2" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2023-09-13T16:42:03+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "funding": [ - { - "url": "https://github.com/sponsors/tillkruss", - "type": "github" - } - ], - "time": "2023-03-02T18:32:04+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { - "name": "psr/cache", - "version": "3.0.0", + "name": "psr/clock", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": "^7.0 || ^8.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { - "Psr\\Cache\\": "src/" + "Psr\\Clock\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4086,16 +4324,20 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for caching libraries", + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", "keywords": [ - "cache", + "clock", + "now", "psr", - "psr-6" + "psr-20", + "time" ], "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" }, - "time": "2021-02-03T23:26:27+00:00" + "time": "2022-11-25T14:36:26+00:00" }, { "name": "psr/container", @@ -4202,16 +4444,16 @@ }, { "name": "psr/http-client", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { @@ -4248,26 +4490,26 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-10T20:12:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -4291,7 +4533,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -4303,9 +4545,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -4362,30 +4604,30 @@ }, { "name": "psr/log", - "version": "1.1.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4406,9 +4648,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:46:02+00:00" }, { "name": "psr/simple-cache", @@ -4463,25 +4705,25 @@ }, { "name": "psy/psysh", - "version": "v0.11.16", + "version": "v0.12.4", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "151b145906804eea8e5d71fea23bfb470c904bfb" + "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/151b145906804eea8e5d71fea23bfb470c904bfb", - "reference": "151b145906804eea8e5d71fea23bfb470c904bfb", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", + "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", "shasum": "" }, "require": { "ext-json": "*", "ext-tokenizer": "*", - "nikic/php-parser": "^4.0 || ^3.1", - "php": "^8.0 || ^7.0.8", - "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" }, "conflict": { "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" @@ -4492,8 +4734,7 @@ "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", "ext-pdo-sqlite": "The doc command requires SQLite to work.", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." }, "bin": [ "bin/psysh" @@ -4501,7 +4742,11 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.11.x-dev" + "dev-main": "0.12.x-dev" + }, + "bamarni-bin": { + "bin-links": false, + "forward-command": false } }, "autoload": { @@ -4533,33 +4778,33 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.16" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" }, - "time": "2023-04-26T12:53:57+00:00" + "time": "2024-06-10T01:18:23+00:00" }, { "name": "qirolab/laravel-themer", - "version": "2.1.0", + "version": "2.3.3", "source": { "type": "git", "url": "https://github.com/qirolab/laravel-themer.git", - "reference": "f643b371411e063fb0d42bffb1586df6ec70972b" + "reference": "d1ad2eae0068c026fcb5ebfe5391c280f5ace361" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/qirolab/laravel-themer/zipball/f643b371411e063fb0d42bffb1586df6ec70972b", - "reference": "f643b371411e063fb0d42bffb1586df6ec70972b", + "url": "https://api.github.com/repos/qirolab/laravel-themer/zipball/d1ad2eae0068c026fcb5ebfe5391c280f5ace361", + "reference": "d1ad2eae0068c026fcb5ebfe5391c280f5ace361", "shasum": "" }, "require": { "facade/ignition-contracts": "^1.0", - "illuminate/support": "^9.19|^10.0", + "illuminate/support": "^9.19|^10.0|^11.0", "php": ">=7.1.0" }, "require-dev": { - "orchestra/testbench": "^7.0|^8.0", - "phpunit/phpunit": "^8.3|^9.0", - "vimeo/psalm": "^4.0" + "orchestra/testbench": "^7.0|^8.0|^9.0", + "phpunit/phpunit": "^8.3|^9.0|^10.5", + "vimeo/psalm": "^4.0|^5.22" }, "type": "library", "extra": { @@ -4597,7 +4842,7 @@ ], "support": { "issues": "https://github.com/qirolab/laravel-themer/issues", - "source": "https://github.com/qirolab/laravel-themer/tree/2.1.0" + "source": "https://github.com/qirolab/laravel-themer/tree/2.3.3" }, "funding": [ { @@ -4605,7 +4850,7 @@ "type": "other" } ], - "time": "2023-02-14T12:31:27+00:00" + "time": "2024-05-15T15:29:58+00:00" }, { "name": "ralouphie/getallheaders", @@ -4742,20 +4987,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.4", + "version": "4.7.6", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "60a4c63ab724854332900504274f6150ff26d286" + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286", - "reference": "60a4c63ab724854332900504274f6150ff26d286", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -4818,7 +5063,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.4" + "source": "https://github.com/ramsey/uuid/tree/4.7.6" }, "funding": [ { @@ -4830,20 +5075,20 @@ "type": "tidelift" } ], - "time": "2023-04-15T23:01:58+00:00" + "time": "2024-04-27T21:32:50+00:00" }, { "name": "sabberworm/php-css-parser", - "version": "8.4.0", + "version": "v8.6.0", "source": { "type": "git", - "url": "https://github.com/sabberworm/PHP-CSS-Parser.git", - "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30" + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sabberworm/PHP-CSS-Parser/zipball/e41d2140031d533348b2192a83f02d8dd8a71d30", - "reference": "e41d2140031d533348b2192a83f02d8dd8a71d30", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70", "shasum": "" }, "require": { @@ -4851,13 +5096,17 @@ "php": ">=5.6.20" }, "require-dev": { - "codacy/coverage": "^1.4", - "phpunit/phpunit": "^4.8.36" + "phpunit/phpunit": "^5.7.27" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, "autoload": { "psr-4": { "Sabberworm\\CSS\\": "src/" @@ -4870,6 +5119,14 @@ "authors": [ { "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" } ], "description": "Parser for CSS Files written in PHP", @@ -4880,23 +5137,23 @@ "stylesheet" ], "support": { - "issues": "https://github.com/sabberworm/PHP-CSS-Parser/issues", - "source": "https://github.com/sabberworm/PHP-CSS-Parser/tree/8.4.0" + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0" }, - "time": "2021-12-11T13:40:54+00:00" + "time": "2024-07-01T07:33:21+00:00" }, { "name": "socialiteproviders/discord", - "version": "4.1.2", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Discord.git", - "reference": "11f6a8ded5b1948723886f2e5413b91139fcce6b" + "reference": "c71c379acfdca5ba4aa65a3db5ae5222852a919c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Discord/zipball/11f6a8ded5b1948723886f2e5413b91139fcce6b", - "reference": "11f6a8ded5b1948723886f2e5413b91139fcce6b", + "url": "https://api.github.com/repos/SocialiteProviders/Discord/zipball/c71c379acfdca5ba4aa65a3db5ae5222852a919c", + "reference": "c71c379acfdca5ba4aa65a3db5ae5222852a919c", "shasum": "" }, "require": { @@ -4933,30 +5190,30 @@ "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2023-02-01T08:54:49+00:00" + "time": "2023-07-24T23:28:47+00:00" }, { "name": "socialiteproviders/manager", - "version": "v4.3.0", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "47402cbc5b7ef445317e799bf12fd5a12062206c" + "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/47402cbc5b7ef445317e799bf12fd5a12062206c", - "reference": "47402cbc5b7ef445317e799bf12fd5a12062206c", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/dea5190981c31b89e52259da9ab1ca4e2b258b21", + "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21", "shasum": "" }, "require": { - "illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0", - "laravel/socialite": "~5.0", - "php": "^7.4 || ^8.0" + "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "laravel/socialite": "^5.5", + "php": "^8.0" }, "require-dev": { "mockery/mockery": "^1.2", - "phpunit/phpunit": "^6.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { @@ -5007,33 +5264,33 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2023-01-26T23:11:27+00:00" + "time": "2024-05-04T07:57:39+00:00" }, { "name": "spatie/laravel-activitylog", - "version": "4.7.3", + "version": "4.8.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-activitylog.git", - "reference": "ec65a478a909b8df1b4f0c3c45de2592ca7639e5" + "reference": "eb6f37dd40af950ce10cf5280f0acfa3e08c3bff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/ec65a478a909b8df1b4f0c3c45de2592ca7639e5", - "reference": "ec65a478a909b8df1b4f0c3c45de2592ca7639e5", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/eb6f37dd40af950ce10cf5280f0acfa3e08c3bff", + "reference": "eb6f37dd40af950ce10cf5280f0acfa3e08c3bff", "shasum": "" }, "require": { - "illuminate/config": "^8.0 || ^9.0 || ^10.0", - "illuminate/database": "^8.69 || ^9.27 || ^10.0", - "illuminate/support": "^8.0 || ^9.0 || ^10.0", - "php": "^8.0", + "illuminate/config": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "illuminate/database": "^8.69 || ^9.27 || ^10.0 || ^11.0", + "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "php": "^8.1", "spatie/laravel-package-tools": "^1.6.3" }, "require-dev": { "ext-json": "*", - "orchestra/testbench": "^6.23 || ^7.0 || ^8.0", - "pestphp/pest": "^1.20" + "orchestra/testbench": "^6.23 || ^7.0 || ^8.0 || ^9.0", + "pestphp/pest": "^1.20 || ^2.0" }, "type": "library", "extra": { @@ -5086,7 +5343,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-activitylog/issues", - "source": "https://github.com/spatie/laravel-activitylog/tree/4.7.3" + "source": "https://github.com/spatie/laravel-activitylog/tree/4.8.0" }, "funding": [ { @@ -5098,24 +5355,24 @@ "type": "github" } ], - "time": "2023-01-25T17:04:51+00:00" + "time": "2024-03-08T22:28:17+00:00" }, { "name": "spatie/laravel-package-tools", - "version": "1.15.0", + "version": "1.16.4", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "efab1844b8826443135201c4443690f032c3d533" + "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/efab1844b8826443135201c4443690f032c3d533", - "reference": "efab1844b8826443135201c4443690f032c3d533", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", + "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", "shasum": "" }, "require": { - "illuminate/contracts": "^9.28|^10.0", + "illuminate/contracts": "^9.28|^10.0|^11.0", "php": "^8.0" }, "require-dev": { @@ -5150,7 +5407,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.15.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.4" }, "funding": [ { @@ -5158,39 +5415,39 @@ "type": "github" } ], - "time": "2023-04-27T08:09:01+00:00" + "time": "2024-03-20T07:29:11+00:00" }, { "name": "spatie/laravel-permission", - "version": "5.10.1", + "version": "6.9.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-permission.git", - "reference": "d08b3ffc5870cce4a47a39f22174947b33c191ae" + "reference": "fe973a58b44380d0e8620107259b7bda22f70408" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/d08b3ffc5870cce4a47a39f22174947b33c191ae", - "reference": "d08b3ffc5870cce4a47a39f22174947b33c191ae", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/fe973a58b44380d0e8620107259b7bda22f70408", + "reference": "fe973a58b44380d0e8620107259b7bda22f70408", "shasum": "" }, "require": { - "illuminate/auth": "^7.0|^8.0|^9.0|^10.0", - "illuminate/container": "^7.0|^8.0|^9.0|^10.0", - "illuminate/contracts": "^7.0|^8.0|^9.0|^10.0", - "illuminate/database": "^7.0|^8.0|^9.0|^10.0", - "php": "^7.3|^8.0" + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0", + "php": "^8.0" }, "require-dev": { - "orchestra/testbench": "^5.0|^6.0|^7.0|^8.0", - "phpunit/phpunit": "^9.4", - "predis/predis": "^1.1" + "laravel/passport": "^11.0|^12.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.4|^10.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.x-dev", - "dev-master": "5.x-dev" + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" }, "laravel": { "providers": [ @@ -5218,7 +5475,7 @@ "role": "Developer" } ], - "description": "Permission handling for Laravel 6.0 and up", + "description": "Permission handling for Laravel 8.0 and up", "homepage": "https://github.com/spatie/laravel-permission", "keywords": [ "acl", @@ -5232,7 +5489,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-permission/issues", - "source": "https://github.com/spatie/laravel-permission/tree/5.10.1" + "source": "https://github.com/spatie/laravel-permission/tree/6.9.0" }, "funding": [ { @@ -5240,35 +5497,37 @@ "type": "github" } ], - "time": "2023-04-12T17:08:32+00:00" + "time": "2024-06-22T23:04:52+00:00" }, { "name": "spatie/laravel-query-builder", - "version": "5.2.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-query-builder.git", - "reference": "7b3911f61dcaa27804b80f30b73a468a9539e850" + "reference": "69a6fd38c1515e42aec0df10d8adb8465f2f1f79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/7b3911f61dcaa27804b80f30b73a468a9539e850", - "reference": "7b3911f61dcaa27804b80f30b73a468a9539e850", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/69a6fd38c1515e42aec0df10d8adb8465f2f1f79", + "reference": "69a6fd38c1515e42aec0df10d8adb8465f2f1f79", "shasum": "" }, "require": { - "illuminate/database": "^9.0|^10.0", - "illuminate/http": "^9.0|^10.0", - "illuminate/support": "^9.0|^10.0", - "php": "^8.0", + "illuminate/database": "^10.0|^11.0", + "illuminate/http": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", + "php": "^8.2", "spatie/laravel-package-tools": "^1.11" }, "require-dev": { "ext-json": "*", "mockery/mockery": "^1.4", + "nunomaduro/larastan": "^2.0", "orchestra/testbench": "^7.0|^8.0", - "pestphp/pest": "^1.20", - "spatie/laravel-ray": "^1.28" + "pestphp/pest": "^2.0", + "phpunit/phpunit": "^10.0", + "spatie/invade": "^2.0" }, "type": "library", "extra": { @@ -5312,43 +5571,43 @@ "type": "custom" } ], - "time": "2023-02-24T15:48:30+00:00" + "time": "2024-05-21T12:12:10+00:00" }, { "name": "spatie/laravel-settings", - "version": "2.8.3", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-settings.git", - "reference": "9193603a3e02d19af9f2fd0d309ac2469b25d680" + "reference": "395066797823856638a0a2feb243b396a94e22e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-settings/zipball/9193603a3e02d19af9f2fd0d309ac2469b25d680", - "reference": "9193603a3e02d19af9f2fd0d309ac2469b25d680", + "url": "https://api.github.com/repos/spatie/laravel-settings/zipball/395066797823856638a0a2feb243b396a94e22e5", + "reference": "395066797823856638a0a2feb243b396a94e22e5", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/database": "^8.73|^9.0|^10.0", + "illuminate/database": "^8.73|^9.0|^10.0|^11.0", "php": "^7.4|^8.0", "phpdocumentor/type-resolver": "^1.5", "spatie/temporary-directory": "^1.3|^2.0" }, "require-dev": { "ext-redis": "*", + "larastan/larastan": "^2.0", "mockery/mockery": "^1.4", - "nunomaduro/larastan": "^2.0", - "orchestra/testbench": "^6.23|^7.0|^8.0", - "pestphp/pest": "^1.21", - "pestphp/pest-plugin-laravel": "^1.2", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "pestphp/pest": "^1.21|^2.0", + "pestphp/pest-plugin-laravel": "^1.2|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^9.5", - "spatie/laravel-data": "^1.0.0|^2.0.0", - "spatie/pest-plugin-snapshots": "^1.1", - "spatie/phpunit-snapshot-assertions": "^4.2", + "phpunit/phpunit": "^9.5|^10.0", + "spatie/laravel-data": "^1.0.0|^2.0.0|^4.0.0", + "spatie/pest-plugin-snapshots": "^1.1|^2.0", + "spatie/phpunit-snapshot-assertions": "^4.2|^5.0", "spatie/ray": "^1.36" }, "suggest": { @@ -5387,7 +5646,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-settings/issues", - "source": "https://github.com/spatie/laravel-settings/tree/2.8.3" + "source": "https://github.com/spatie/laravel-settings/tree/3.3.2" }, "funding": [ { @@ -5399,32 +5658,32 @@ "type": "github" } ], - "time": "2023-03-30T12:47:39+00:00" + "time": "2024-03-22T07:50:04+00:00" }, { "name": "spatie/laravel-validation-rules", - "version": "3.2.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-validation-rules.git", - "reference": "3cbef3441cb274cd3c12409586f18c3a44d46d0a" + "reference": "b629b0a1049ddfe18e5534717b1627bacfaef208" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-validation-rules/zipball/3cbef3441cb274cd3c12409586f18c3a44d46d0a", - "reference": "3cbef3441cb274cd3c12409586f18c3a44d46d0a", + "url": "https://api.github.com/repos/spatie/laravel-validation-rules/zipball/b629b0a1049ddfe18e5534717b1627bacfaef208", + "reference": "b629b0a1049ddfe18e5534717b1627bacfaef208", "shasum": "" }, "require": { - "illuminate/support": "^8.0|^9.0|^10.0", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", "php": "^8.0" }, "require-dev": { "laravel/pint": "^1.3", - "league/iso3166": "^3.0", + "league/iso3166": "^3.0|^4.3", "myclabs/php-enum": "^1.6", - "orchestra/testbench": "^6.23|^7.0|^8.0", - "phpunit/phpunit": "^9.4", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "pestphp/pest": "^1.23|^2.6", "spatie/enum": "^2.2|^3.0" }, "suggest": { @@ -5464,7 +5723,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-validation-rules/issues", - "source": "https://github.com/spatie/laravel-validation-rules/tree/3.2.2" + "source": "https://github.com/spatie/laravel-validation-rules/tree/3.4.0" }, "funding": [ { @@ -5472,20 +5731,20 @@ "type": "github" } ], - "time": "2023-01-25T16:22:06+00:00" + "time": "2024-03-02T05:57:47+00:00" }, { "name": "spatie/temporary-directory", - "version": "2.1.2", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/spatie/temporary-directory.git", - "reference": "0c804873f6b4042aa8836839dca683c7d0f71831" + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/0c804873f6b4042aa8836839dca683c7d0f71831", - "reference": "0c804873f6b4042aa8836839dca683c7d0f71831", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", "shasum": "" }, "require": { @@ -5521,7 +5780,7 @@ ], "support": { "issues": "https://github.com/spatie/temporary-directory/issues", - "source": "https://github.com/spatie/temporary-directory/tree/2.1.2" + "source": "https://github.com/spatie/temporary-directory/tree/2.2.1" }, "funding": [ { @@ -5533,7 +5792,7 @@ "type": "github" } ], - "time": "2023-04-28T07:47:42+00:00" + "time": "2023-12-25T11:46:58+00:00" }, { "name": "stripe/stripe-php", @@ -5595,51 +5854,122 @@ }, "time": "2022-05-05T17:18:02+00:00" }, + { + "name": "symfony/clock", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7", + "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/console", - "version": "v6.2.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "12288d9f4500f84a4d02254d4aa968b15488476f" + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/12288d9f4500f84a4d02254d4aa968b15488476f", - "reference": "12288d9f4500f84a4d02254d4aa968b15488476f", + "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.4|^6.0" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/lock": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -5673,7 +6003,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.10" + "source": "https://github.com/symfony/console/tree/v7.1.3" }, "funding": [ { @@ -5689,24 +6019,24 @@ "type": "tidelift" } ], - "time": "2023-04-28T13:37:43+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/css-selector", - "version": "v6.2.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "aedf3cb0f5b929ec255d96bbb4909e9932c769e0" + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/aedf3cb0f5b929ec255d96bbb4909e9932c769e0", - "reference": "aedf3cb0f5b929ec255d96bbb4909e9932c769e0", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -5738,7 +6068,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.2.7" + "source": "https://github.com/symfony/css-selector/tree/v7.1.1" }, "funding": [ { @@ -5754,20 +6084,20 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:44:56+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.2.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", - "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -5776,7 +6106,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -5805,7 +6135,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -5821,31 +6151,35 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:25:55+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/error-handler", - "version": "v6.2.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "8b7e9f124640cb0611624a9383176c3e5f7d8cfb" + "reference": "432bb369952795c61ca1def65e078c4a80dad13c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8b7e9f124640cb0611624a9383176c3e5f7d8cfb", - "reference": "8b7e9f124640cb0611624a9383176c3e5f7d8cfb", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.0" + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -5876,7 +6210,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.2.10" + "source": "https://github.com/symfony/error-handler/tree/v7.1.3" }, "funding": [ { @@ -5892,28 +6226,29 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:46:08+00:00" + "time": "2024-07-26T13:02:51+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.2.8", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339" + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/04046f35fd7d72f9646e721fc2ecb8f9c67d3339", - "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/event-dispatcher-contracts": "^2|^3" + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", @@ -5921,17 +6256,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/error-handler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^5.4|^6.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -5959,7 +6290,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" }, "funding": [ { @@ -5975,33 +6306,30 @@ "type": "tidelift" } ], - "time": "2023-03-20T16:06:02+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.2.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd" + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", - "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", "shasum": "" }, "require": { "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -6038,7 +6366,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" }, "funding": [ { @@ -6054,27 +6382,27 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/finder", - "version": "v6.2.7", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb" + "reference": "717c6329886f32dc65e27461f80f2a465412fdca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/20808dc6631aecafbe67c186af5dcb370be3a0eb", - "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb", + "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", + "reference": "717c6329886f32dc65e27461f80f2a465412fdca", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "symfony/filesystem": "^6.0" + "symfony/filesystem": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6102,7 +6430,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.2.7" + "source": "https://github.com/symfony/finder/tree/v7.1.3" }, "funding": [ { @@ -6118,28 +6446,32 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:57:23+00:00" + "time": "2024-07-24T07:08:44+00:00" }, { "name": "symfony/http-client", - "version": "v6.2.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "3f5545a91c8e79dedd1a06c4b04e1682c80c42f9" + "reference": "b79858aa7a051ea791b0d50269a234a0b50cb231" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/3f5545a91c8e79dedd1a06c4b04e1682c80c42f9", - "reference": "3f5545a91c8e79dedd1a06c4b04e1682c80c42f9", + "url": "https://api.github.com/repos/symfony/http-client/zipball/b79858aa7a051ea791b0d50269a234a0b50cb231", + "reference": "b79858aa7a051ea791b0d50269a234a0b50cb231", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-client-contracts": "^3", - "symfony/service-contracts": "^1.0|^2|^3" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3.4.1", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" }, "provide": { "php-http/async-client-implementation": "*", @@ -6152,14 +6484,16 @@ "amphp/http-client": "^4.2.1", "amphp/http-tunnel": "^1.0", "amphp/socket": "^1.1", - "guzzlehttp/promises": "^1.4", + "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6190,7 +6524,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.2.10" + "source": "https://github.com/symfony/http-client/tree/v7.1.3" }, "funding": [ { @@ -6206,32 +6540,29 @@ "type": "tidelift" } ], - "time": "2023-04-20T13:12:48+00:00" + "time": "2024-07-17T06:10:24+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.2.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "df2ecd6cb70e73c1080e6478aea85f5f4da2c48b" + "reference": "20414d96f391677bf80078aa55baece78b82647d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/df2ecd6cb70e73c1080e6478aea85f5f4da2c48b", - "reference": "df2ecd6cb70e73c1080e6478aea85f5f4da2c48b", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", + "reference": "20414d96f391677bf80078aa55baece78b82647d", "shasum": "" }, "require": { "php": ">=8.1" }, - "suggest": { - "symfony/http-client-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -6271,7 +6602,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" }, "funding": [ { @@ -6287,41 +6618,40 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.2.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "49adbb92bcb4e3c2943719d2756271e8b9602acc" + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/49adbb92bcb4e3c2943719d2756271e8b9602acc", - "reference": "49adbb92bcb4e3c2943719d2756271e8b9602acc", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.1" + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.2" + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4" }, "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", - "symfony/mime": "^5.4|^6.0", - "symfony/rate-limiter": "^5.2|^6.0" - }, - "suggest": { - "symfony/mime": "To use the file extension guesser" + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6349,7 +6679,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.2.10" + "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" }, "funding": [ { @@ -6365,74 +6695,77 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:46:08+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.2.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "81064a65a5496f17d2b6984f6519406f98864215" + "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/81064a65a5496f17d2b6984f6519406f98864215", - "reference": "81064a65a5496f17d2b6984f6519406f98864215", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db9702f3a04cc471ec8c70e881825db26ac5f186", + "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^6.1", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^5.4.21|^6.2.7", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.4", - "symfony/config": "<6.1", - "symfony/console": "<5.4", - "symfony/dependency-injection": "<6.2", - "symfony/doctrine-bridge": "<5.4", - "symfony/form": "<5.4", - "symfony/http-client": "<5.4", - "symfony/mailer": "<5.4", - "symfony/messenger": "<5.4", - "symfony/translation": "<5.4", - "symfony/twig-bridge": "<5.4", - "symfony/validator": "<5.4", - "twig/twig": "<2.13" + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.0.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^5.4|^6.0", - "symfony/config": "^6.1", - "symfony/console": "^5.4|^6.0", - "symfony/css-selector": "^5.4|^6.0", - "symfony/dependency-injection": "^6.2", - "symfony/dom-crawler": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", - "symfony/process": "^5.4|^6.0", - "symfony/routing": "^5.4|^6.0", - "symfony/stopwatch": "^5.4|^6.0", - "symfony/translation": "^5.4|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.0.4" }, "type": "library", "autoload": { @@ -6460,7 +6793,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.2.10" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.3" }, "funding": [ { @@ -6476,28 +6809,32 @@ "type": "tidelift" } ], - "time": "2023-04-28T13:50:28+00:00" + "time": "2024-07-26T14:58:15+00:00" }, { "name": "symfony/intl", - "version": "v6.2.10", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "860c99e53149d22df1900d3aefdaeb17adb7669d" + "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/860c99e53149d22df1900d3aefdaeb17adb7669d", - "reference": "860c99e53149d22df1900d3aefdaeb17adb7669d", + "url": "https://api.github.com/repos/symfony/intl/zipball/66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c", + "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/string": "<7.1" }, "require-dev": { - "symfony/filesystem": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0" + "symfony/filesystem": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6505,7 +6842,8 @@ "Symfony\\Component\\Intl\\": "" }, "exclude-from-classmap": [ - "/Tests/" + "/Tests/", + "/Resources/data/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -6541,7 +6879,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v6.2.10" + "source": "https://github.com/symfony/intl/tree/v7.1.1" }, "funding": [ { @@ -6557,42 +6895,43 @@ "type": "tidelift" } ], - "time": "2023-04-14T16:23:31+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/mailer", - "version": "v6.2.8", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "bfcfa015c67e19c6fdb7ca6fe70700af1e740a17" + "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/bfcfa015c67e19c6fdb7ca6fe70700af1e740a17", - "reference": "bfcfa015c67e19c6fdb7ca6fe70700af1e740a17", + "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee", + "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee", "shasum": "" }, "require": { "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=8.1", + "php": ">=8.2", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/mime": "^6.2", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/http-kernel": "<5.4", - "symfony/messenger": "<6.2", - "symfony/mime": "<6.2", - "symfony/twig-bridge": "<6.2.1" + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/messenger": "^6.2", - "symfony/twig-bridge": "^6.2" + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6620,7 +6959,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.2.8" + "source": "https://github.com/symfony/mailer/tree/v7.1.2" }, "funding": [ { @@ -6636,28 +6975,32 @@ "type": "tidelift" } ], - "time": "2023-03-14T15:00:05+00:00" + "time": "2024-06-28T08:00:31+00:00" }, { "name": "symfony/mailgun-mailer", - "version": "v6.2.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "2c9d47b11cc154d2db3f571030cd965d128de1a8" + "reference": "dac02fe68e9306849703025511c56f10701696a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/2c9d47b11cc154d2db3f571030cd965d128de1a8", - "reference": "2c9d47b11cc154d2db3f571030cd965d128de1a8", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/dac02fe68e9306849703025511c56f10701696a8", + "reference": "dac02fe68e9306849703025511c56f10701696a8", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/mailer": "^5.4.21|^6.2.7" + "php": ">=8.2", + "symfony/mailer": "^6.4|^7.0" + }, + "conflict": { + "symfony/http-foundation": "<6.4" }, "require-dev": { - "symfony/http-client": "^5.4|^6.0" + "symfony/http-client": "^6.4|^7.0", + "symfony/webhook": "^6.4|^7.0" }, "type": "symfony-mailer-bridge", "autoload": { @@ -6685,7 +7028,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v6.2.10" + "source": "https://github.com/symfony/mailgun-mailer/tree/v7.1.3" }, "funding": [ { @@ -6701,24 +7044,24 @@ "type": "tidelift" } ], - "time": "2023-04-14T16:23:31+00:00" + "time": "2024-07-04T11:20:59+00:00" }, { "name": "symfony/mime", - "version": "v6.2.10", + "version": "v7.1.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "b6c137fc53a9f7c4c951cd3f362b3734c7a97723" + "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/b6c137fc53a9f7c4c951cd3f362b3734c7a97723", - "reference": "b6c137fc53a9f7c4c951cd3f362b3734c7a97723", + "url": "https://api.github.com/repos/symfony/mime/zipball/26a00b85477e69a4bab63b66c5dce64f18b0cbfc", + "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -6726,17 +7069,18 @@ "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<5.4", - "symfony/serializer": "<6.2" + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.4|^6.0", - "symfony/serializer": "^6.2" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" }, "type": "library", "autoload": { @@ -6768,7 +7112,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.2.10" + "source": "https://github.com/symfony/mime/tree/v7.1.2" }, "funding": [ { @@ -6784,20 +7128,20 @@ "type": "tidelift" } ], - "time": "2023-04-19T09:54:16+00:00" + "time": "2024-06-28T10:03:55+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { @@ -6811,9 +7155,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6850,7 +7191,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, "funding": [ { @@ -6866,20 +7207,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", "shasum": "" }, "require": { @@ -6890,9 +7231,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6931,7 +7269,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" }, "funding": [ { @@ -6947,20 +7285,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", - "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", "shasum": "" }, "require": { @@ -6973,9 +7311,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -7018,7 +7353,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" }, "funding": [ { @@ -7034,20 +7369,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", "shasum": "" }, "require": { @@ -7058,9 +7393,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -7102,7 +7434,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" }, "funding": [ { @@ -7118,20 +7450,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -7145,9 +7477,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -7185,7 +7514,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -7201,20 +7530,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + "reference": "10112722600777e02d2745716b70c5db4ca70442" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", + "reference": "10112722600777e02d2745716b70c5db4ca70442", "shasum": "" }, "require": { @@ -7222,9 +7551,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -7261,7 +7587,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" }, "funding": [ { @@ -7277,20 +7603,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -7298,9 +7624,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -7344,7 +7667,83 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" }, "funding": [ { @@ -7360,20 +7759,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-06-19T12:35:24+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.27.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", - "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", "shasum": "" }, "require": { @@ -7387,9 +7786,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -7426,7 +7822,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" }, "funding": [ { @@ -7442,24 +7838,24 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/process", - "version": "v6.2.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e" + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e", - "reference": "b34cdbc9c5e75d45a3703e63a48ad07aafa8bf2e", + "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -7487,7 +7883,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.2.10" + "source": "https://github.com/symfony/process/tree/v7.1.3" }, "funding": [ { @@ -7503,45 +7899,38 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:56:57+00:00" + "time": "2024-07-26T12:44:47+00:00" }, { "name": "symfony/routing", - "version": "v6.2.8", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "69062e2823f03b82265d73a966999660f0e1e404" + "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/69062e2823f03b82265d73a966999660f0e1e404", - "reference": "69062e2823f03b82265d73a966999660f0e1e404", + "url": "https://api.github.com/repos/symfony/routing/zipball/8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", + "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "doctrine/annotations": "<1.12", - "symfony/config": "<6.2", - "symfony/dependency-injection": "<5.4", - "symfony/yaml": "<5.4" + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^6.2", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", - "symfony/yaml": "^5.4|^6.0" - }, - "suggest": { - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -7575,7 +7964,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.2.8" + "source": "https://github.com/symfony/routing/tree/v7.1.3" }, "funding": [ { @@ -7591,36 +7980,34 @@ "type": "tidelift" } ], - "time": "2023-03-14T15:00:05+00:00" + "time": "2024-07-17T06:10:24+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.2.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "a8c9cedf55f314f3a186041d19537303766df09a" + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", - "reference": "a8c9cedf55f314f3a186041d19537303766df09a", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", "shasum": "" }, "require": { "php": ">=8.1", - "psr/container": "^2.0" + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -7660,7 +8047,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" }, "funding": [ { @@ -7676,38 +8063,39 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/string", - "version": "v6.2.8", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef" + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef", - "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef", + "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", + "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": "<2.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0", - "symfony/http-client": "^5.4|^6.0", - "symfony/intl": "^6.2", - "symfony/translation-contracts": "^2.0|^3.0", - "symfony/var-exporter": "^5.4|^6.0" + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -7746,7 +8134,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.8" + "source": "https://github.com/symfony/string/tree/v7.1.3" }, "funding": [ { @@ -7762,58 +8150,54 @@ "type": "tidelift" } ], - "time": "2023-03-20T16:06:02+00:00" + "time": "2024-07-22T10:25:37+00:00" }, { "name": "symfony/translation", - "version": "v6.2.8", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "817535dbb1721df8b3a8f2489dc7e50bcd6209b5" + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/817535dbb1721df8b3a8f2489dc7e50bcd6209b5", - "reference": "817535dbb1721df8b3a8f2489dc7e50bcd6209b5", + "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1", + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.3|^3.0" + "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { - "symfony/config": "<5.4", - "symfony/console": "<5.4", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/twig-bundle": "<5.4", - "symfony/yaml": "<5.4" + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" }, "provide": { "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "nikic/php-parser": "^4.13", + "nikic/php-parser": "^4.18|^5.0", "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0", - "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/finder": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2.0|^3.0", - "symfony/http-kernel": "^5.4|^6.0", - "symfony/intl": "^5.4|^6.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^5.4|^6.0", - "symfony/service-contracts": "^1.1.2|^2|^3", - "symfony/yaml": "^5.4|^6.0" - }, - "suggest": { - "nikic/php-parser": "To use PhpAstExtractor", - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" + "symfony/routing": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -7844,7 +8228,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.2.8" + "source": "https://github.com/symfony/translation/tree/v7.1.3" }, "funding": [ { @@ -7860,32 +8244,29 @@ "type": "tidelift" } ], - "time": "2023-03-31T09:14:44+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.2.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "dfec258b9dd17a6b24420d464c43bffe347441c8" + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dfec258b9dd17a6b24420d464c43bffe347441c8", - "reference": "dfec258b9dd17a6b24420d464c43bffe347441c8", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", "shasum": "" }, "require": { "php": ">=8.1" }, - "suggest": { - "symfony/translation-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.3-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -7925,7 +8306,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.2.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" }, "funding": [ { @@ -7941,28 +8322,28 @@ "type": "tidelift" } ], - "time": "2023-03-01T10:32:47+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/uid", - "version": "v6.2.7", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0" + "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0", - "reference": "d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0", + "url": "https://api.github.com/repos/symfony/uid/zipball/bb59febeecc81528ff672fad5dab7f06db8c8277", + "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-uuid": "^1.15" }, "require-dev": { - "symfony/console": "^5.4|^6.0" + "symfony/console": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -7999,7 +8380,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.2.7" + "source": "https://github.com/symfony/uid/tree/v7.1.1" }, "funding": [ { @@ -8015,41 +8396,36 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:44:56+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.2.10", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "41a750a23412ca76fdbbf5096943b4134272c1ab" + "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41a750a23412ca76fdbbf5096943b4134272c1ab", - "reference": "41a750a23412ca76fdbbf5096943b4134272c1ab", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/86af4617cca75a6e28598f49ae0690f3b9d4591f", + "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "phpunit/phpunit": "<5.4.3", - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^5.4|^6.0", - "symfony/process": "^5.4|^6.0", - "symfony/uid": "^5.4|^6.0", - "twig/twig": "^2.13|^3.0.4" - }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.0.4" }, "bin": [ "Resources/bin/var-dump-server" @@ -8087,7 +8463,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.2.10" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.3" }, "funding": [ { @@ -8103,27 +8479,27 @@ "type": "tidelift" } ], - "time": "2023-04-18T13:46:08+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "2.2.6", + "version": "v2.2.7", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c" + "reference": "83ee6f38df0a63106a9e4536e3060458b74ccedb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/c42125b83a4fa63b187fdf29f9c93cb7733da30c", - "reference": "c42125b83a4fa63b187fdf29f9c93cb7733da30c", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/83ee6f38df0a63106a9e4536e3060458b74ccedb", + "reference": "83ee6f38df0a63106a9e4536e3060458b74ccedb", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "php": "^5.5 || ^7.0 || ^8.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" @@ -8154,37 +8530,37 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.6" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.2.7" }, - "time": "2023-01-03T09:29:04+00:00" + "time": "2023-12-08T13:03:43+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.5.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", - "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.0.2", - "php": "^7.1.3 || ^8.0", - "phpoption/phpoption": "^1.8", - "symfony/polyfill-ctype": "^1.23", - "symfony/polyfill-mbstring": "^1.23.1", - "symfony/polyfill-php80": "^1.23.1" + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-filter": "*", - "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" }, "suggest": { "ext-filter": "Required to use the boolean validator." @@ -8193,10 +8569,10 @@ "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { - "dev-master": "5.5-dev" + "dev-master": "5.6-dev" } }, "autoload": { @@ -8228,7 +8604,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" }, "funding": [ { @@ -8240,7 +8616,7 @@ "type": "tidelift" } ], - "time": "2022-10-16T01:01:54+00:00" + "time": "2024-07-20T21:52:34+00:00" }, { "name": "voku/portable-ascii", @@ -8376,39 +8752,46 @@ }, { "name": "yajra/laravel-datatables-oracle", - "version": "v9.21.2", + "version": "v11.1.3", "source": { "type": "git", "url": "https://github.com/yajra/laravel-datatables.git", - "reference": "a7fd01f06282923e9c63fa27fe6b391e21dc321a" + "reference": "78ecbc43025a24b92c307664c0cf395fea56c215" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/a7fd01f06282923e9c63fa27fe6b391e21dc321a", - "reference": "a7fd01f06282923e9c63fa27fe6b391e21dc321a", + "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/78ecbc43025a24b92c307664c0cf395fea56c215", + "reference": "78ecbc43025a24b92c307664c0cf395fea56c215", "shasum": "" }, "require": { - "illuminate/database": "5.8.*|^6|^7|^8|^9", - "illuminate/filesystem": "5.8.*|^6|^7|^8|^9", - "illuminate/http": "5.8.*|^6|^7|^8|^9", - "illuminate/support": "5.8.*|^6|^7|^8|^9", - "illuminate/view": "5.8.*|^6|^7|^8|^9", - "php": "^7.1.3|^8" + "illuminate/database": "^11", + "illuminate/filesystem": "^11", + "illuminate/http": "^11", + "illuminate/support": "^11", + "illuminate/view": "^11", + "php": "^8.2" }, "require-dev": { - "orchestra/testbench": "^3.8|^4.0|^5.0|^6.0|^7.0" + "algolia/algoliasearch-client-php": "^3.4.1", + "larastan/larastan": "^2.9.1", + "laravel/pint": "^1.14", + "laravel/scout": "^10.8.3", + "meilisearch/meilisearch-php": "^1.6.1", + "orchestra/testbench": "^9", + "rector/rector": "^1.0" }, "suggest": { "yajra/laravel-datatables-buttons": "Plugin for server-side exporting of dataTables.", "yajra/laravel-datatables-editor": "Plugin to use DataTables Editor (requires a license).", + "yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.", "yajra/laravel-datatables-fractal": "Plugin for server-side response using Fractal.", "yajra/laravel-datatables-html": "Plugin for server-side HTML builder of dataTables." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.0-dev" + "dev-master": "11.x-dev" }, "laravel": { "providers": [ @@ -8437,62 +8820,59 @@ "email": "aqangeles@gmail.com" } ], - "description": "jQuery DataTables API for Laravel 5|6|7|8|9", + "description": "jQuery DataTables API for Laravel", "keywords": [ "datatables", "jquery", - "laravel" + "laravel", + "yajra" ], "support": { "issues": "https://github.com/yajra/laravel-datatables/issues", - "source": "https://github.com/yajra/laravel-datatables/tree/v9.21.2" + "source": "https://github.com/yajra/laravel-datatables/tree/v11.1.3" }, "funding": [ { - "url": "https://www.paypal.me/yajra", - "type": "custom" - }, - { - "url": "https://www.patreon.com/yajra", - "type": "patreon" + "url": "https://github.com/sponsors/yajra", + "type": "github" } ], - "time": "2022-07-12T04:48:03+00:00" + "time": "2024-07-15T04:47:52+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.8.1", + "version": "v3.13.5", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "aff3235fecb4104203b1e62c32239c56530eee32" + "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/aff3235fecb4104203b1e62c32239c56530eee32", - "reference": "aff3235fecb4104203b1e62c32239c56530eee32", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/92d86be45ee54edff735e46856f64f14b6a8bb07", + "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07", "shasum": "" }, "require": { - "illuminate/routing": "^9|^10", - "illuminate/session": "^9|^10", - "illuminate/support": "^9|^10", - "maximebf/debugbar": "^1.18.2", + "illuminate/routing": "^9|^10|^11", + "illuminate/session": "^9|^10|^11", + "illuminate/support": "^9|^10|^11", + "maximebf/debugbar": "~1.22.0", "php": "^8.0", - "symfony/finder": "^6" + "symfony/finder": "^6|^7" }, "require-dev": { "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^5|^6|^7|^8", - "phpunit/phpunit": "^8.5.30|^9.0", + "orchestra/testbench-dusk": "^5|^6|^7|^8|^9", + "phpunit/phpunit": "^9.6|^10.5", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.8-dev" + "dev-master": "3.13-dev" }, "laravel": { "providers": [ @@ -8531,7 +8911,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.8.1" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.5" }, "funding": [ { @@ -8543,90 +8923,20 @@ "type": "github" } ], - "time": "2023-02-21T14:21:02+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "require-dev": { - "doctrine/coding-standard": "^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2024-04-12T11:20:37+00:00" }, { "name": "fakerphp/faker", - "version": "v1.21.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d" + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/92efad6a967f0b79c499705c69b662f738cc9e4d", - "reference": "92efad6a967f0b79c499705c69b662f738cc9e4d", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", "shasum": "" }, "require": { @@ -8652,11 +8962,6 @@ "ext-mbstring": "Required for multibyte Unicode string functionality." }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "v1.21-dev" - } - }, "autoload": { "psr-4": { "Faker\\": "src/Faker/" @@ -8679,22 +8984,22 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.21.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" }, - "time": "2022-12-13T13:54:32+00:00" + "time": "2024-01-02T13:46:09+00:00" }, { "name": "filp/whoops", - "version": "2.15.2", + "version": "2.15.4", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73" + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", - "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", + "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", + "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", "shasum": "" }, "require": { @@ -8744,7 +9049,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.2" + "source": "https://github.com/filp/whoops/tree/2.15.4" }, "funding": [ { @@ -8752,7 +9057,7 @@ "type": "github" } ], - "time": "2023-04-12T12:00:00+00:00" + "time": "2023-11-03T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -8807,27 +9112,28 @@ }, { "name": "laravel/sail", - "version": "v1.21.5", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "27af207bb1c53faddcba34c7528b3e969f6a646d" + "reference": "48d89608a3bb5be763c9bb87121d31e7da27c1cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/27af207bb1c53faddcba34c7528b3e969f6a646d", - "reference": "27af207bb1c53faddcba34c7528b3e969f6a646d", + "url": "https://api.github.com/repos/laravel/sail/zipball/48d89608a3bb5be763c9bb87121d31e7da27c1cb", + "reference": "48d89608a3bb5be763c9bb87121d31e7da27c1cb", "shasum": "" }, "require": { - "illuminate/console": "^8.0|^9.0|^10.0", - "illuminate/contracts": "^8.0|^9.0|^10.0", - "illuminate/support": "^8.0|^9.0|^10.0", - "php": "^7.3|^8.0", - "symfony/yaml": "^6.0" + "illuminate/console": "^9.52.16|^10.0|^11.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0", + "illuminate/support": "^9.52.16|^10.0|^11.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0", + "symfony/yaml": "^6.0|^7.0" }, "require-dev": { - "orchestra/testbench": "^6.0|^7.0|^8.0", + "orchestra/testbench": "^7.0|^8.0|^9.0", "phpstan/phpstan": "^1.10" }, "bin": [ @@ -8835,9 +9141,6 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - }, "laravel": { "providers": [ "Laravel\\Sail\\SailServiceProvider" @@ -8868,29 +9171,31 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2023-04-24T13:29:38+00:00" + "time": "2024-07-22T14:36:50+00:00" }, { "name": "maximebf/debugbar", - "version": "v1.18.2", + "version": "v1.22.3", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "17dcf3f6ed112bb85a37cf13538fd8de49f5c274" + "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/17dcf3f6ed112bb85a37cf13538fd8de49f5c274", - "reference": "17dcf3f6ed112bb85a37cf13538fd8de49f5c274", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", + "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", "shasum": "" }, "require": { - "php": "^7.1|^8", + "php": "^7.2|^8", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4|^5|^6" + "symfony/var-dumper": "^4|^5|^6|^7" }, "require-dev": { - "phpunit/phpunit": ">=7.5.20 <10.0", + "dbrekelmans/bdi": "^1", + "phpunit/phpunit": "^8|^9", + "symfony/panther": "^1|^2.1", "twig/twig": "^1.38|^2.7|^3.0" }, "suggest": { @@ -8901,7 +9206,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-master": "1.22-dev" } }, "autoload": { @@ -8932,44 +9237,44 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.18.2" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.3" }, - "time": "2023-02-04T15:27:00+00:00" + "time": "2024-04-03T19:39:26+00:00" }, { "name": "mockery/mockery", - "version": "1.5.1", + "version": "1.6.12", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e" + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/e92dcc83d5a51851baf5f5591d32cb2b16e3684e", - "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": "^7.3 || ^8.0" + "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.3" + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, "autoload": { - "psr-0": { - "Mockery": "library/" + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", @@ -8980,12 +9285,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -9003,23 +9316,26 @@ "testing" ], "support": { + "docs": "https://docs.mockery.io/", "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.5.1" + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "time": "2022-09-07T15:32:08+00:00" + "time": "2024-05-16T03:13:13+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.1", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", - "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { @@ -9027,11 +9343,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -9057,7 +9374,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { @@ -9065,49 +9382,58 @@ "type": "tidelift" } ], - "time": "2023-03-08T13:26:56+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { "name": "nunomaduro/collision", - "version": "v6.4.0", + "version": "v8.3.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "f05978827b9343cba381ca05b8c7deee346b6015" + "reference": "b49f5b2891ce52726adfd162841c69d4e4c84229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f05978827b9343cba381ca05b8c7deee346b6015", - "reference": "f05978827b9343cba381ca05b8c7deee346b6015", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b49f5b2891ce52726adfd162841c69d4e4c84229", + "reference": "b49f5b2891ce52726adfd162841c69d4e4c84229", "shasum": "" }, "require": { - "filp/whoops": "^2.14.5", - "php": "^8.0.0", - "symfony/console": "^6.0.2" + "filp/whoops": "^2.15.4", + "nunomaduro/termwind": "^2.0.1", + "php": "^8.2.0", + "symfony/console": "^7.1.2" + }, + "conflict": { + "laravel/framework": "<11.0.0 || >=12.0.0", + "phpunit/phpunit": "<10.5.1 || >=12.0.0" }, "require-dev": { - "brianium/paratest": "^6.4.1", - "laravel/framework": "^9.26.1", - "laravel/pint": "^1.1.1", - "nunomaduro/larastan": "^1.0.3", - "nunomaduro/mock-final-classes": "^1.1.0", - "orchestra/testbench": "^7.7", - "phpunit/phpunit": "^9.5.23", - "spatie/ignition": "^1.4.1" + "larastan/larastan": "^2.9.8", + "laravel/framework": "^11.16.0", + "laravel/pint": "^1.16.2", + "laravel/sail": "^1.30.2", + "laravel/sanctum": "^4.0.2", + "laravel/tinker": "^2.9.0", + "orchestra/testbench-core": "^9.2.1", + "pestphp/pest": "^2.34.9 || ^3.0.0", + "sebastian/environment": "^6.1.0 || ^7.0.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-develop": "6.x-dev" - }, "laravel": { "providers": [ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" } }, "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], "psr-4": { "NunoMaduro\\Collision\\": "src/" } @@ -9153,24 +9479,25 @@ "type": "patreon" } ], - "time": "2023-01-03T12:54:54+00:00" + "time": "2024-07-16T22:41:01+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -9211,9 +9538,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -9268,35 +9601,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.26", + "version": "11.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", - "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/19b6365ab8b59a64438c0c3f4241feeb480c9861", + "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", + "nikic/php-parser": "^5.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.0", + "phpunit/php-text-template": "^4.0", + "sebastian/code-unit-reverse-lookup": "^4.0", + "sebastian/complexity": "^4.0", + "sebastian/environment": "^7.0", + "sebastian/lines-of-code": "^3.0", + "sebastian/version": "^5.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -9305,7 +9638,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "11.0-dev" } }, "autoload": { @@ -9333,7 +9666,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.5" }, "funding": [ { @@ -9341,32 +9675,32 @@ "type": "github" } ], - "time": "2023-03-06T12:58:08+00:00" + "time": "2024-07-03T05:05:37+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6ed896bf50bbbfe4d504a33ed5886278c78e4a26", + "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -9393,7 +9727,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.1" }, "funding": [ { @@ -9401,28 +9736,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2024-07-03T05:06:37+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -9430,7 +9765,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -9456,7 +9791,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, "funding": [ { @@ -9464,32 +9800,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2024-07-03T05:07:44+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -9515,7 +9851,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, "funding": [ { @@ -9523,32 +9860,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -9574,7 +9911,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, "funding": [ { @@ -9582,54 +9920,51 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { "name": "phpunit/phpunit", - "version": "9.6.7", + "version": "11.2.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" + "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", - "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a7a29e8d3113806f18f99d670f580a30e8ffff39", + "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.5", + "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.1", + "sebastian/comparator": "^6.0.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.1.3", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.0.1", + "sebastian/version": "^5.0.1" }, "suggest": { - "ext-soap": "To be able to generate mocks based on WSDL files", - "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -9637,7 +9972,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.6-dev" + "dev-main": "11.2-dev" } }, "autoload": { @@ -9669,7 +10004,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.8" }, "funding": [ { @@ -9685,32 +10020,32 @@ "type": "tidelift" } ], - "time": "2023-04-14T08:58:40+00:00" + "time": "2024-07-18T14:56:37+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -9733,7 +10068,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { @@ -9741,32 +10077,32 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "6bb7d09d6623567178cf54126afa9c2310114268" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", + "reference": "6bb7d09d6623567178cf54126afa9c2310114268", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -9789,7 +10125,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" }, "funding": [ { @@ -9797,32 +10134,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2024-07-03T04:44:28+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -9844,7 +10181,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -9852,34 +10190,36 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "131942b86d3587291067a94f295498ab6ac79c20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/131942b86d3587291067a94f295498ab6ac79c20", + "reference": "131942b86d3587291067a94f295498ab6ac79c20", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -9918,7 +10258,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.1" }, "funding": [ { @@ -9926,33 +10267,33 @@ "type": "github" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2024-07-03T04:48:07+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -9975,7 +10316,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -9983,33 +10325,33 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -10041,7 +10383,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -10049,27 +10392,27 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "5.1.5", + "version": "7.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", - "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-posix": "*" @@ -10077,7 +10420,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -10096,7 +10439,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -10104,7 +10447,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" }, "funding": [ { @@ -10112,34 +10456,34 @@ "type": "github" } ], - "time": "2023-02-03T06:03:51+00:00" + "time": "2024-07-03T04:54:44+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "6.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -10181,7 +10525,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" }, "funding": [ { @@ -10189,38 +10534,35 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2024-07-03T04:56:19+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -10239,13 +10581,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -10253,33 +10596,33 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -10302,7 +10645,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { @@ -10310,34 +10654,34 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -10359,7 +10703,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { @@ -10367,32 +10712,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -10414,7 +10759,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { @@ -10422,32 +10768,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.5", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", - "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -10477,62 +10823,8 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-02-03T06:07:39+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" }, "funding": [ { @@ -10540,32 +10832,32 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-07-03T05:10:34+00:00" }, { "name": "sebastian/type", - "version": "3.2.1", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", - "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb6a6566f9589e86661291d13eba708cce5eb4aa", + "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -10588,7 +10880,8 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.0.1" }, "funding": [ { @@ -10596,29 +10889,29 @@ "type": "github" } ], - "time": "2023-02-03T06:13:03+00:00" + "time": "2024-07-03T05:11:49+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/45c9debb7d039ce9b97de2f749c2cf5832a06ac4", + "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -10641,7 +10934,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.1" }, "funding": [ { @@ -10649,20 +10943,20 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2024-07-03T05:13:08+00:00" }, { "name": "spatie/backtrace", - "version": "1.4.0", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "ec4dd16476b802dbdc6b4467f84032837e316b8c" + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/ec4dd16476b802dbdc6b4467f84032837e316b8c", - "reference": "ec4dd16476b802dbdc6b4467f84032837e316b8c", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", "shasum": "" }, "require": { @@ -10670,6 +10964,7 @@ }, "require-dev": { "ext-json": "*", + "laravel/serializable-closure": "^1.3", "phpunit/phpunit": "^9.3", "spatie/phpunit-snapshot-assertions": "^4.2", "symfony/var-dumper": "^5.1" @@ -10699,7 +10994,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.4.0" + "source": "https://github.com/spatie/backtrace/tree/1.6.2" }, "funding": [ { @@ -10711,43 +11006,117 @@ "type": "other" } ], - "time": "2023-03-04T08:57:24+00:00" + "time": "2024-07-22T08:21:24+00:00" + }, + { + "name": "spatie/error-solutions", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/error-solutions.git", + "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/ae7393122eda72eed7cc4f176d1e96ea444f2d67", + "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "illuminate/broadcasting": "^10.0|^11.0", + "illuminate/cache": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", + "livewire/livewire": "^2.11|^3.3.5", + "openai-php/client": "^0.10.1", + "orchestra/testbench": "^7.0|8.22.3|^9.0", + "pestphp/pest": "^2.20", + "phpstan/phpstan": "^1.11", + "psr/simple-cache": "^3.0", + "psr/simple-cache-implementation": "^3.0", + "spatie/ray": "^1.28", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Ignition\\": "legacy/ignition", + "Spatie\\ErrorSolutions\\": "src", + "Spatie\\LaravelIgnition\\": "legacy/laravel-ignition" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ruben Van Assche", + "email": "ruben@spatie.be", + "role": "Developer" + } + ], + "description": "This is my package error-solutions", + "homepage": "https://github.com/spatie/error-solutions", + "keywords": [ + "error-solutions", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/error-solutions/issues", + "source": "https://github.com/spatie/error-solutions/tree/1.1.1" + }, + "funding": [ + { + "url": "https://github.com/Spatie", + "type": "github" + } + ], + "time": "2024-07-25T11:06:04+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.3.6", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "530ac81255af79f114344286e4275f8869c671e2" + "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/530ac81255af79f114344286e4275f8869c671e2", - "reference": "530ac81255af79f114344286e4275f8869c671e2", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234", + "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0|^10.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", "php": "^8.0", - "spatie/backtrace": "^1.2", - "symfony/http-foundation": "^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", - "symfony/process": "^5.2|^6.0", - "symfony/var-dumper": "^5.2|^6.0" + "spatie/backtrace": "^1.6.1", + "symfony/http-foundation": "^5.2|^6.0|^7.0", + "symfony/mime": "^5.2|^6.0|^7.0", + "symfony/process": "^5.2|^6.0|^7.0", + "symfony/var-dumper": "^5.2|^6.0|^7.0" }, "require-dev": { - "dms/phpunit-arraysubset-asserts": "^0.3.0", - "pestphp/pest": "^1.20", + "dms/phpunit-arraysubset-asserts": "^0.5.0", + "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/phpunit-snapshot-assertions": "^4.0" + "spatie/phpunit-snapshot-assertions": "^4.0|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.1.x-dev" + "dev-main": "1.3.x-dev" } }, "autoload": { @@ -10772,7 +11141,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.3.6" + "source": "https://github.com/spatie/flare-client-php/tree/1.7.0" }, "funding": [ { @@ -10780,41 +11149,41 @@ "type": "github" } ], - "time": "2023-04-12T07:57:12+00:00" + "time": "2024-06-12T14:39:14+00:00" }, { "name": "spatie/ignition", - "version": "1.6.0", + "version": "1.15.0", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "fbcfcabc44e506e40c4d72fd4ddf465e272a600e" + "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/fbcfcabc44e506e40c4d72fd4ddf465e272a600e", - "reference": "fbcfcabc44e506e40c4d72fd4ddf465e272a600e", + "url": "https://api.github.com/repos/spatie/ignition/zipball/e3a68e137371e1eb9edc7f78ffa733f3b98991d2", + "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", "php": "^8.0", - "spatie/backtrace": "^1.4", - "spatie/flare-client-php": "^1.1", - "symfony/console": "^5.4|^6.0", - "symfony/var-dumper": "^5.4|^6.0" + "spatie/error-solutions": "^1.0", + "spatie/flare-client-php": "^1.7", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "require-dev": { - "illuminate/cache": "^9.52", + "illuminate/cache": "^9.52|^10.0|^11.0", "mockery/mockery": "^1.4", - "pestphp/pest": "^1.20", + "pestphp/pest": "^1.20|^2.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "psr/simple-cache-implementation": "*", - "symfony/cache": "^6.2", - "symfony/process": "^5.4|^6.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", "vlucas/phpdotenv": "^5.5" }, "suggest": { @@ -10863,45 +11232,46 @@ "type": "github" } ], - "time": "2023-04-27T08:40:07+00:00" + "time": "2024-06-12T14:55:22+00:00" }, { "name": "spatie/laravel-ignition", - "version": "1.6.4", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc" + "reference": "3c067b75bfb50574db8f7e2c3978c65eed71126c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc", - "reference": "1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/3c067b75bfb50574db8f7e2c3978c65eed71126c", + "reference": "3c067b75bfb50574db8f7e2c3978c65eed71126c", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "illuminate/support": "^8.77|^9.27", - "monolog/monolog": "^2.3", - "php": "^8.0", - "spatie/flare-client-php": "^1.0.1", - "spatie/ignition": "^1.4.1", - "symfony/console": "^5.0|^6.0", - "symfony/var-dumper": "^5.0|^6.0" + "illuminate/support": "^10.0|^11.0", + "php": "^8.1", + "spatie/ignition": "^1.15", + "symfony/console": "^6.2.3|^7.0", + "symfony/var-dumper": "^6.2.3|^7.0" }, "require-dev": { - "filp/whoops": "^2.14", - "livewire/livewire": "^2.8|dev-develop", - "mockery/mockery": "^1.4", - "nunomaduro/larastan": "^1.0", - "orchestra/testbench": "^6.23|^7.0", - "pestphp/pest": "^1.20", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "spatie/laravel-ray": "^1.27" + "livewire/livewire": "^2.11|^3.3.5", + "mockery/mockery": "^1.5.1", + "openai-php/client": "^0.8.1", + "orchestra/testbench": "8.22.3|^9.0", + "pestphp/pest": "^2.34", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan-deprecation-rules": "^1.1.1", + "phpstan/phpstan-phpunit": "^1.3.16", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "psr/simple-cache-implementation": "Needed to cache solutions from OpenAI" }, "type": "library", "extra": { @@ -10953,34 +11323,31 @@ "type": "github" } ], - "time": "2023-01-03T19:28:04+00:00" + "time": "2024-06-12T15:01:18+00:00" }, { "name": "symfony/yaml", - "version": "v6.2.10", + "version": "v7.1.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "61916f3861b1e9705b18cfde723921a71dd1559d" + "reference": "fa34c77015aa6720469db7003567b9f772492bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/61916f3861b1e9705b18cfde723921a71dd1559d", - "reference": "61916f3861b1e9705b18cfde723921a71dd1559d", + "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", + "reference": "fa34c77015aa6720469db7003567b9f772492bf2", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "symfony/console": "^6.4|^7.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -11011,7 +11378,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.2.10" + "source": "https://github.com/symfony/yaml/tree/v7.1.1" }, "funding": [ { @@ -11027,20 +11394,20 @@ "type": "tidelift" } ], - "time": "2023-04-28T13:25:36+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -11069,7 +11436,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -11077,7 +11444,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], @@ -11086,12 +11453,14 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.1", - "ext-intl": "*" + "php": "^8.2", + "ext-intl": "*", + "ext-mysqli": "*", + "ext-curl": "*" }, "platform-dev": [], "platform-overrides": { - "php": "8.1" + "php": "8.2" }, - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.6.0" } From 98b5b1876dd518e1181d53bfba6845a8505d31ab Mon Sep 17 00:00:00 2001 From: Jens <34610614+AGuyNamedJens@users.noreply.github.com> Date: Fri, 2 Aug 2024 09:26:10 +0200 Subject: [PATCH 458/514] [Feature] Discord role on purchase (#984) * Initial commit, addition to the Settings * [Feat] Discord Settings added * Refactor: Create a generic add/remove role function to DiscordUser * [Feat] Add or Remove role on Creation or Deletion --- .../Controllers/Admin/ServerController.php | 40 ++++++++++++++++++- .../Controllers/Auth/SocialiteController.php | 11 +---- app/Http/Controllers/ServerController.php | 34 +++++++++++++++- app/Models/DiscordUser.php | 36 +++++++++++++++++ app/Settings/DiscordSettings.php | 14 +++++++ ...2024_06_25_133205_add_discord_settings.php | 18 +++++++++ 6 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 database/settings/2024_06_25_133205_add_discord_settings.php diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php index b2775bb1c..087dbf83e 100644 --- a/app/Http/Controllers/Admin/ServerController.php +++ b/app/Http/Controllers/Admin/ServerController.php @@ -5,6 +5,7 @@ use App\Http\Controllers\Controller; use App\Models\Server; use App\Models\User; +use App\Settings\DiscordSettings; use App\Settings\LocaleSettings; use App\Settings\PterodactylSettings; use App\Classes\PterodactylClient; @@ -73,7 +74,7 @@ public function edit(Server $server) * @param Request $request * @param Server $server */ - public function update(Request $request, Server $server) + public function update(Request $request, Server $server, DiscordSettings $discord_settings) { $request->validate([ 'identifier' => 'required|string', @@ -92,8 +93,29 @@ public function update(Request $request, Server $server) return redirect()->back()->with('error', 'Failed to update server owner on pterodactyl'); } + // Attempt to remove/add roles respectively + try { + if($discord_settings->role_on_purchase) { + // remove the role from the old owner + $oldOwner = User::findOrFail($server->user_id); + $discordUser = $oldOwner->discordUser; + if ($discordUser && $oldOwner->servers->count() <= 1) { + $discordUser->addOrRemoveRole('remove', $discord_settings->role_id_on_purchase); + } + + // add the role to the new owner + $discordUser = $user->discordUser; + if ($discordUser && $user->servers->count() >= 1) { + $discordUser->addOrRemoveRole('add', $discord_settings->role_id_on_purchase); + } + } + } catch (Exception $e) { + log::debug('Failed to update discord roles' . $e->getMessage()); + } + // update the owner on the database $server->user_id = $user->id; + } catch (Exception $e) { return redirect()->back()->with('error', 'Internal Server Error'); } @@ -115,10 +137,24 @@ public function update(Request $request, Server $server) * @param Server $server * @return RedirectResponse|Response */ - public function destroy(Server $server) + public function destroy(Server $server, DiscordSettings $discord_settings) { $this->checkPermission(self::DELETE_PERMISSION); try { + // Remove role from discord + try { + if($discord_settings->role_on_purchase) { + $user = User::findOrFail($server->user_id); + $discordUser = $user->discordUser; + if($discordUser && $user->servers->count() <= 1) { + $discordUser->addOrRemoveRole('remove', $discord_settings->role_id_on_purchase); + } + } + } catch (Exception $e) { + log::debug('Failed to update discord roles' . $e->getMessage()); + } + + // Attempt to remove the server from pterodactyl $server->delete(); return redirect()->route('admin.servers.index')->with('success', __('Server removed')); diff --git a/app/Http/Controllers/Auth/SocialiteController.php b/app/Http/Controllers/Auth/SocialiteController.php index 4e495d435..02904c51f 100644 --- a/app/Http/Controllers/Auth/SocialiteController.php +++ b/app/Http/Controllers/Auth/SocialiteController.php @@ -73,15 +73,8 @@ public function callback(DiscordSettings $discord_settings, UserSettings $user_s //give user a role in the discord server if (! empty($roleId)) { - $response = Http::withHeaders( - [ - 'Authorization' => 'Bot '.$botToken, - 'Content-Type' => 'application/json', - ] - )->put( - "https://discord.com/api/guilds/{$guildId}/members/{$discord->id}/roles/{$roleId}", - ['access_token' => $discord->token] - ); + // Function addOrRemoveRole is defined in app/Models/DiscordUser.php + $user->discordUser->addOrRemoveRole('add', $roleId); } } diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index f9f76e19f..7f5919b4f 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -8,7 +8,9 @@ use App\Models\Pterodactyl\Node; use App\Models\Product; use App\Models\Server; +use App\Models\User; use App\Notifications\ServerCreationError; +use App\Settings\DiscordSettings; use Carbon\Carbon; use App\Settings\UserSettings; use App\Settings\ServerSettings; @@ -21,6 +23,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Request as FacadesRequest; class ServerController extends Controller @@ -176,7 +179,7 @@ private function validateConfigurationRules(UserSettings $user_settings, ServerS } /** Store a newly created resource in storage. */ - public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $generalSettings) + public function store(Request $request, UserSettings $user_settings, ServerSettings $server_settings, GeneralSettings $generalSettings, DiscordSettings $discord_settings) { /** @var Location $location */ /** @var Egg $egg */ @@ -235,6 +238,20 @@ public function store(Request $request, UserSettings $user_settings, ServerSetti // Charge first billing cycle $request->user()->decrement('credits', $server->product->price); + // Add role from discord + try { + if($discord_settings->role_on_purchase) { + $user = $request->user(); + $discordUser = $user->discordUser; + if($discordUser && $user->servers->count() >= 1) { + $discordUser->addOrRemoveRole('add', $discord_settings->role_id_on_purchase); + } + } + } catch (Exception $e) { + log::debug('Failed to update discord roles' . $e->getMessage()); + } + + return redirect()->route('servers.index')->with('success', __('Server created')); } @@ -266,9 +283,22 @@ private function serverCreationFailed(Response $response, Server $server) } /** Remove the specified resource from storage. */ - public function destroy(Server $server) + public function destroy(Server $server, DiscordSettings $discord_settings) { try { + // Remove role from discord + try { + if($discord_settings->role_on_purchase) { + $user = User::findOrFail($server->user_id); + $discordUser = $user->discordUser; + if($discordUser && $user->servers->count() <= 1) { + $discordUser->addOrRemoveRole('remove', $discord_settings->role_id_on_purchase); + } + } + } catch (Exception $e) { + log::debug('Failed to update discord roles' . $e->getMessage()); + } + $server->delete(); return redirect()->route('servers.index')->with('success', __('Server removed')); diff --git a/app/Models/DiscordUser.php b/app/Models/DiscordUser.php index 03051ea32..900e8c625 100644 --- a/app/Models/DiscordUser.php +++ b/app/Models/DiscordUser.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Support\Facades\Http; class DiscordUser extends Model { @@ -42,4 +43,39 @@ public function getAvatar() { return 'https://cdn.discordapp.com/avatars/'.$this->id.'/'.$this->avatar.'.png'; } + + + /** + * Add or remove role on discord server + * @param string $action The action to perform (add or remove) + * @param string $role_id The Role ID to add or remove + * @return mixed + */ + public function addOrRemoveRole(string $action, string $role_id): mixed + { + $discordSettings = app('discord_settings'); + return match ($action) { + 'add' => Http::withHeaders( + [ + 'Authorization' => 'Bot ' . $discordSettings->bot_token, + 'Content-Type' => 'application/json', + 'X-Audit-Log-Reason' => 'Role added by panel' + ] + )->put( + "https://discord.com/api/guilds/{$discordSettings->guild_id}/members/{$this->id}/roles/{$discordSettings->role_id}", + ['access_token' => $discordSettings->token] + ), + 'remove' => Http::withHeaders( + [ + 'Authorization' => 'Bot ' . $discordSettings->bot_token, + 'Content-Type' => 'application/json', + 'X-Audit-Log-Reason' => 'Role removed by panel' + ] + )->remove( + "https://discord.com/api/guilds/{$discordSettings->guild_id}/members/{$this->id}/roles/{$discordSettings->role_id}", + ['access_token' => $discordSettings->token] + ), + default => null, + }; + } } diff --git a/app/Settings/DiscordSettings.php b/app/Settings/DiscordSettings.php index 79981c71b..69284e01d 100644 --- a/app/Settings/DiscordSettings.php +++ b/app/Settings/DiscordSettings.php @@ -12,6 +12,8 @@ class DiscordSettings extends Settings public ?string $guild_id; public ?string $invite_url; public ?string $role_id; + public ?bool $role_on_purchase; + public ?string $role_id_on_purchase; public static function group(): string { @@ -31,6 +33,8 @@ public static function getValidations() 'guild_id' => 'nullable|string', 'invite_url' => 'nullable|string|url', 'role_id' => 'nullable|string', + 'role_on_purchase' => 'nullable|string', + 'role_id_on_purchase' => 'nullable|string', ]; } @@ -73,6 +77,16 @@ public static function getOptionInputData() 'type' => 'string', 'description' => 'The role ID for your Discord server.', ], + 'role_on_purchase' => [ + 'label' => 'Role on Purchase', + 'type' => 'boolean', + 'description' => 'Give the user a role on purchase (removes when user has no active servers)', + ], + 'role_id_on_purchase' => [ + 'label' => 'Role ID on Purchase', + 'type' => 'string', + 'description' => 'The role ID for your Discord server on purchase.', + ], ]; } } diff --git a/database/settings/2024_06_25_133205_add_discord_settings.php b/database/settings/2024_06_25_133205_add_discord_settings.php new file mode 100644 index 000000000..b73729f7d --- /dev/null +++ b/database/settings/2024_06_25_133205_add_discord_settings.php @@ -0,0 +1,18 @@ +<?php + +use Spatie\LaravelSettings\Migrations\SettingsMigration; + +class AddDiscordSettings extends SettingsMigration +{ + public function up(): void + { + $this->migrator->add('discord.role_on_purchase', null); + $this->migrator->add('discord.role_id_on_purchase', null); + } + + public function down(): void + { + $this->migrator->delete('discord.role_on_purchase'); + $this->migrator->delete('discord.role_id_on_purchase'); + } +} From 730c9dd4a962a6f781abafccdc7cdb18d22ed07a Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Wed, 7 Aug 2024 21:08:13 -0600 Subject: [PATCH 459/514] feat: Added select & deselect all eggs --- .../views/admin/products/create.blade.php | 18 +++++++++++++++++- .../views/admin/products/edit.blade.php | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/themes/default/views/admin/products/create.blade.php b/themes/default/views/admin/products/create.blade.php index c6c5427f6..bfbfcdcfc 100644 --- a/themes/default/views/admin/products/create.blade.php +++ b/themes/default/views/admin/products/create.blade.php @@ -307,7 +307,13 @@ class="custom-select @error('nodes') is-invalid @enderror" name="nodes[]" <div class="form-group"> - <label for="eggs">{{ __('Eggs') }}</label> + <div class="d-flex justify-content-between align-items-center mb-2"> + <label for="eggs" class="mb-0">{{ __('Eggs') }}</label> + <div> + <button type="button" id="select-all-eggs" class="btn btn-sm btn-secondary">{{ __('Select All') }}</button> + <button type="button" id="deselect-all-eggs" class="btn btn-sm btn-secondary ml-2">{{ __('Deselect All') }}</button> + </div> + </div> <select id="eggs" style="width:100%" class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" multiple="multiple" autocomplete="off"> @@ -350,6 +356,16 @@ class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" document.addEventListener('DOMContentLoaded', function() { $('[data-toggle="popover"]').popover(); $('.custom-select').select2(); + + document.getElementById('select-all-eggs').addEventListener('click', function() { + $('#eggs option').prop('selected', true); + $('#eggs').trigger('change'); + }); + + document.getElementById('deselect-all-eggs').addEventListener('click', function() { + $('#eggs option').prop('selected', false); + $('#eggs').trigger('change'); + }); }); </script> @endsection diff --git a/themes/default/views/admin/products/edit.blade.php b/themes/default/views/admin/products/edit.blade.php index 8f7649580..b371ea7fb 100644 --- a/themes/default/views/admin/products/edit.blade.php +++ b/themes/default/views/admin/products/edit.blade.php @@ -315,7 +315,13 @@ class="custom-select @error('nodes') is-invalid @enderror" name="nodes[]" </div> <div class="form-group"> - <label for="eggs">Eggs</label> + <div class="d-flex justify-content-between align-items-center mb-2"> + <label for="eggs" class="mb-0">{{ __('Eggs') }}</label> + <div> + <button type="button" id="select-all-eggs" class="btn btn-sm btn-secondary">{{ __('Select All') }}</button> + <button type="button" id="deselect-all-eggs" class="btn btn-sm btn-secondary ml-2">{{ __('Deselect All') }}</button> + </div> + </div> <select id="eggs" style="width:100%" class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" multiple="multiple" autocomplete="off"> @@ -351,6 +357,16 @@ class="custom-select @error('eggs') is-invalid @enderror" name="eggs[]" <script> document.addEventListener('DOMContentLoaded', (event) => { $('.custom-select').select2(); + + document.getElementById('select-all-eggs').addEventListener('click', function() { + $('#eggs option').prop('selected', true); + $('#eggs').trigger('change'); + }); + + document.getElementById('deselect-all-eggs').addEventListener('click', function() { + $('#eggs option').prop('selected', false); + $('#eggs').trigger('change'); + }); }) </script> <script> From 700a46037735009a64af8affd33730f07669f434 Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Sat, 17 Aug 2024 03:15:20 +0000 Subject: [PATCH 460/514] style: :lipstick: Fixed color and size of the footer --- public/installer/styles.css | 8 ++++++++ public/installer/views/layout-bottom.php | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/public/installer/styles.css b/public/installer/styles.css index b7db03605..c6d52afd0 100644 --- a/public/installer/styles.css +++ b/public/installer/styles.css @@ -956,6 +956,14 @@ video { outline-color: #ca8a04; } +.left-0 { + left: 0px; +} + +.right-0 { + right: 0px; +} + @media (min-width: 640px) { .sm\:w-auto { width: auto; diff --git a/public/installer/views/layout-bottom.php b/public/installer/views/layout-bottom.php index 944ac0107..60e1efca0 100644 --- a/public/installer/views/layout-bottom.php +++ b/public/installer/views/layout-bottom.php @@ -4,8 +4,8 @@ <!-- any middle view here --> - <footer class="fixed bottom-0 w-full bg-gray-800 bg-opacity-20 text-center py-2 text-xs"> - © 2024 CtrlPanel | installer v2.0.0 + <footer class="fixed bottom-0 left-0 right-0 bg-[#1D2125] bg-opacity-20 text-center py-2 text-xs"> + © <?php echo date("Y"); ?> CtrlPanel | installer v2.0.0 </footer> </body> From cf6eba954ba22a7eb79af02065f19c40505cee20 Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Sat, 17 Aug 2024 03:16:29 +0000 Subject: [PATCH 461/514] style: :lipstick: Subtitles are now centered --- public/installer/views/layout-top.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/installer/views/layout-top.php b/public/installer/views/layout-top.php index 42003869b..60c084b89 100644 --- a/public/installer/views/layout-top.php +++ b/public/installer/views/layout-top.php @@ -64,7 +64,7 @@ function cardStart($title, $subtitle = null): string </div> <div class='border-4 border-[#2E373B] bg-[#242A2E] rounded-2xl p-6 pt-3 mx-2'> <h2 class='text-xl text-center mb-2'>$title</h2>" - . (isset($subtitle) ? "<p class='text-neutral-400 mb-1'>$subtitle</p>" : ""); + . (isset($subtitle) ? "<p class='text-neutral-400 mb-1 text-center'>$subtitle</p>" : ""); } ?> <!-- any middle view here --> From 116d82180557af6911980285d9625cc1df1b8835 Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Sat, 17 Aug 2024 03:20:05 +0000 Subject: [PATCH 462/514] style: :lipstick: Changed color of the back button, UTF arrows replaced with SVG icons --- public/installer/styles.css | 12 ++++++++++-- public/installer/views/admin-creation.php | 15 +++++++++------ .../installer/views/dashboard-configuration.php | 15 +++++++++------ public/installer/views/database-configuration.php | 15 +++++++++------ public/installer/views/database-migration.php | 15 +++++++++------ public/installer/views/email-configuration.php | 15 +++++++++------ public/installer/views/mandatory-checks.php | 15 +++++++++------ .../installer/views/pterodactyl-configuration.php | 15 +++++++++------ public/installer/views/redis-configuration.php | 15 +++++++++------ public/installer/views/timezone-configuration.php | 15 +++++++++------ 10 files changed, 91 insertions(+), 56 deletions(-) diff --git a/public/installer/styles.css b/public/installer/styles.css index c6d52afd0..c9ed45534 100644 --- a/public/installer/styles.css +++ b/public/installer/styles.css @@ -649,6 +649,14 @@ video { margin-top: 1rem; } +.mr-1 { + margin-right: 0.25rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + .box-border { box-sizing: border-box; } @@ -758,9 +766,9 @@ video { background-color: rgb(34 197 94 / 0.9); } -.bg-red-300 { +.bg-red-500 { --tw-bg-opacity: 1; - background-color: rgb(252 165 165 / var(--tw-bg-opacity)); + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); } .bg-sky-500 { diff --git a/public/installer/views/admin-creation.php b/public/installer/views/admin-creation.php index ef528555f..694574b4e 100644 --- a/public/installer/views/admin-creation.php +++ b/public/installer/views/admin-creation.php @@ -44,22 +44,25 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } ?> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </div> </form> diff --git a/public/installer/views/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php index 856f8df45..46b8f5b8e 100644 --- a/public/installer/views/dashboard-configuration.php +++ b/public/installer/views/dashboard-configuration.php @@ -38,22 +38,25 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } ?> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </div> </form> diff --git a/public/installer/views/database-configuration.php b/public/installer/views/database-configuration.php index ca34a4a00..3542afee8 100644 --- a/public/installer/views/database-configuration.php +++ b/public/installer/views/database-configuration.php @@ -71,22 +71,25 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } ?> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </div> </form> diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index 85f0e6779..764eb5f91 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -19,22 +19,25 @@ if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } ?> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </div> </form> diff --git a/public/installer/views/email-configuration.php b/public/installer/views/email-configuration.php index 55ea14363..c548a9e53 100644 --- a/public/installer/views/email-configuration.php +++ b/public/installer/views/email-configuration.php @@ -70,15 +70,17 @@ if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } @@ -90,8 +92,9 @@ </button> </a> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </div> </form> diff --git a/public/installer/views/mandatory-checks.php b/public/installer/views/mandatory-checks.php index 4713c4581..40733eacf 100644 --- a/public/installer/views/mandatory-checks.php +++ b/public/installer/views/mandatory-checks.php @@ -51,22 +51,25 @@ if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } ?> <a href="?step=next"> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="mandatory"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </a> </div> diff --git a/public/installer/views/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php index 5d74bd444..6ed17a6e8 100644 --- a/public/installer/views/pterodactyl-configuration.php +++ b/public/installer/views/pterodactyl-configuration.php @@ -48,22 +48,25 @@ if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } ?> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </div> </form> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index 04e4586d3..ad5fdc575 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -47,22 +47,25 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } ?> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="redisSetup"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </div> </form> diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index 7806c76b2..66dca3817 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -37,22 +37,25 @@ if ($_SESSION['is_previous_button_available'] == true) { ?> <a href="?step=previous"> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-red-300 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-red-500 hover:bg-red-400 shadow-red-200 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-red-500"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> </a> <?php } else { ?> - <button type="button" id="backButton" class="px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> - ← Back + <button type="button" id="backButton" class="flex items-center px-4 py-2 font-bold rounded-md bg-gray-200 text-gray-500 shadow-inner cursor-not-allowed" disabled> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="mr-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9.586 4l-6.586 6.586a2 2 0 0 0 0 2.828l6.586 6.586a2 2 0 0 0 2.18 .434l.145 -.068a2 2 0 0 0 1.089 -1.78v-2.586h7a2 2 0 0 0 2 -2v-4l-.005 -.15a2 2 0 0 0 -1.995 -1.85l-7 -.001v-2.585a2 2 0 0 0 -3.414 -1.414z" /></svg> + Back </button> <?php } ?> - <button type="submit" class="px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="timezoneConfig"> - Next → + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + Next + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> </div> </form> From 6c6bf2129bb1cbd1d7650b9ce97b6ba1025749be Mon Sep 17 00:00:00 2001 From: jameskitt616 <52933658+jameskitt616@users.noreply.github.com> Date: Sun, 22 Sep 2024 11:16:11 +0200 Subject: [PATCH 463/514] Replace mysql/redis installer host value fields with placeholders --- public/installer/views/database-configuration.php | 2 +- public/installer/views/redis-configuration.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/installer/views/database-configuration.php b/public/installer/views/database-configuration.php index 3542afee8..b0ba51ad7 100644 --- a/public/installer/views/database-configuration.php +++ b/public/installer/views/database-configuration.php @@ -25,7 +25,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m <div class="flex flex-col mb-3"> <label for="databasehost">Database Host</label> <input x-model="databasehost" id="databasehost" name="databasehost" type="text" required - value="<?php echo(determineIfRunningInDocker() ? 'mysql' : '127.0.0.1') ?>" + placeholder="e.g. localhost, 10.x.x.x, mysql, 192.168.x.x" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index ad5fdc575..bd475c6dd 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -17,7 +17,7 @@ <div class="flex flex-col mb-3"> <label for="redishost">Redis Host</label> <input x-model="redishost" id="redishost" name="redishost" type="text" required - value="<?php echo(determineIfRunningInDocker() ? 'redis' : '127.0.0.1') ?>" + placeholder="e.g. localhost, 127.0.0.1, 10.x.x.x, redis, 192.168.x.x" class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> </div> </div> From 3b60ef4c7491ebac3b15dc9ceaef279d84786752 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 5 Oct 2024 15:01:24 -0600 Subject: [PATCH 464/514] feat: Creation of env file before use --- public/installer/index.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/installer/index.php b/public/installer/index.php index a51c67455..70dac50c6 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -6,6 +6,10 @@ session_start(); +if (!file_exists('../../.env')) { + echo shell_exec('cp ../../.env.example ../../.env'); +} + use DevCoder\DotEnv; // Include systems @@ -32,10 +36,6 @@ exit("The installation has been completed already. Please delete the File 'install.lock' to re-run"); } -if (!file_exists('../../.env')) { - echo run_console('cp ../../.env.example ../../.env'); -} - // load all the .env value in php env (new DotEnv(dirname(__FILE__, 3) . '/.env'))->load(); @@ -85,7 +85,7 @@ include "./views/{$viewName}.php"; include './views/layout-bottom.php'; -// setting / reseting the error message +// setting / resetting the error message $_SESSION['error-message'] = null; ?> \ No newline at end of file From f3cf7107e29c8664e4d4f82db0101cc53714e198 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 5 Oct 2024 15:01:48 -0600 Subject: [PATCH 465/514] feat: Unnecessary import removed --- public/installer/src/forms/smtp.php | 1 - 1 file changed, 1 deletion(-) diff --git a/public/installer/src/forms/smtp.php b/public/installer/src/forms/smtp.php index b2165d7b1..28c6b500f 100644 --- a/public/installer/src/forms/smtp.php +++ b/public/installer/src/forms/smtp.php @@ -3,7 +3,6 @@ use DevCoder\DotEnv; use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\PHPMailer; -use Predis\Client; (new DotEnv(dirname(__FILE__, 5) . '/.env'))->load(); From 491f75a6b849f94ad087ed6c62688399f3a40cdd Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 5 Oct 2024 15:01:56 -0600 Subject: [PATCH 466/514] feat: Added exit --- public/installer/src/functions/installer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/public/installer/src/functions/installer.php b/public/installer/src/functions/installer.php index 169931ea4..b9732eff0 100644 --- a/public/installer/src/functions/installer.php +++ b/public/installer/src/functions/installer.php @@ -11,6 +11,7 @@ function next_step(): void { $_SESSION['current_installation_step']++; header("LOCATION: index.php"); + exit(); } ?> From b90b366df677968ce667d42b0e9ae399a33fa84f Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 5 Oct 2024 15:02:16 -0600 Subject: [PATCH 467/514] fix: Submit name --- public/installer/views/admin-creation.php | 2 +- public/installer/views/dashboard-configuration.php | 2 +- public/installer/views/database-migration.php | 2 +- public/installer/views/email-configuration.php | 2 +- public/installer/views/mandatory-checks.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/installer/views/admin-creation.php b/public/installer/views/admin-creation.php index 694574b4e..96705f831 100644 --- a/public/installer/views/admin-creation.php +++ b/public/installer/views/admin-creation.php @@ -60,7 +60,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m } ?> - <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="createUser"> Next <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> diff --git a/public/installer/views/dashboard-configuration.php b/public/installer/views/dashboard-configuration.php index 46b8f5b8e..f6368e63d 100644 --- a/public/installer/views/dashboard-configuration.php +++ b/public/installer/views/dashboard-configuration.php @@ -54,7 +54,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m } ?> - <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkGeneral"> Next <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> diff --git a/public/installer/views/database-migration.php b/public/installer/views/database-migration.php index 764eb5f91..adaf381d6 100644 --- a/public/installer/views/database-migration.php +++ b/public/installer/views/database-migration.php @@ -35,7 +35,7 @@ } ?> - <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="feedDB"> Next <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> diff --git a/public/installer/views/email-configuration.php b/public/installer/views/email-configuration.php index c548a9e53..7afab1bad 100644 --- a/public/installer/views/email-configuration.php +++ b/public/installer/views/email-configuration.php @@ -92,7 +92,7 @@ </button> </a> - <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkSMTP"> Next <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> diff --git a/public/installer/views/mandatory-checks.php b/public/installer/views/mandatory-checks.php index 40733eacf..24c0aa6a7 100644 --- a/public/installer/views/mandatory-checks.php +++ b/public/installer/views/mandatory-checks.php @@ -67,7 +67,7 @@ } ?> <a href="?step=next"> - <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500"> Next <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> From 5474902bcc8282dfe9d4f389614e840f1a56122c Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 5 Oct 2024 15:02:29 -0600 Subject: [PATCH 468/514] fix: Submit name --- public/installer/views/pterodactyl-configuration.php | 2 +- public/installer/views/redis-configuration.php | 2 +- public/installer/views/timezone-configuration.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/installer/views/pterodactyl-configuration.php b/public/installer/views/pterodactyl-configuration.php index 6ed17a6e8..cacce33f7 100644 --- a/public/installer/views/pterodactyl-configuration.php +++ b/public/installer/views/pterodactyl-configuration.php @@ -64,7 +64,7 @@ } ?> - <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkPtero"> Next <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> diff --git a/public/installer/views/redis-configuration.php b/public/installer/views/redis-configuration.php index bd475c6dd..54bc220fc 100644 --- a/public/installer/views/redis-configuration.php +++ b/public/installer/views/redis-configuration.php @@ -63,7 +63,7 @@ class="px-2 py-1 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-m } ?> - <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="redisSetup"> Next <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index 66dca3817..3766e73ff 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -53,7 +53,7 @@ } ?> - <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="checkDB"> + <button type="submit" class="flex items-center px-4 py-2 font-bold rounded-md bg-sky-500 hover:bg-sky-600 shadow-sky-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-sky-500" name="timezoneConfig"> Next <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor" class="ml-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12.089 3.634a2 2 0 0 0 -1.089 1.78l-.001 2.586h-6.999a2 2 0 0 0 -2 2v4l.005 .15a2 2 0 0 0 1.995 1.85l6.999 -.001l.001 2.587a2 2 0 0 0 3.414 1.414l6.586 -6.586a2 2 0 0 0 0 -2.828l-6.586 -6.586a2 2 0 0 0 -2.18 -.434l-.145 .068z" /></svg> </button> From 2a9866e460bf18ae990e63401192971b9a4ba90c Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 5 Oct 2024 15:04:07 -0600 Subject: [PATCH 469/514] fix: Early creation of env file --- .../installer/views/installation-complete.php | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/public/installer/views/installation-complete.php b/public/installer/views/installation-complete.php index 027784714..48605f71a 100644 --- a/public/installer/views/installation-complete.php +++ b/public/installer/views/installation-complete.php @@ -1,22 +1,27 @@ - <!-- top layout here --> <?php -$lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); -fwrite($lockfile, 'the installation is locked, delete this file to unlock it'); -fclose($lockfile); +// Check if the installation is already locked +if (!file_exists('../../install.lock')) { + echo cardStart( + $title = "Installation Complete!", + $subtitle = "You may navigate to your Dashboard now and log in!" + ); + ?> -echo cardStart( - $title = "Installation Complete!", - $subtitle = "You may navigate to your Dashboard now and log in!" -); -?> + <a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> + <button + class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500"> + Let's Go! + </button> + </a> -<a href="<?php echo getenv('APP_URL'); ?>" class="w-full flex justify-center"> - <button - class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 shadow-green-400 focus:outline-2 focus:outline focus:outline-offset-2 focus:outline-green-500"> - Lets Go! - </button> -</a> + <?php + // Create the lock file after displaying the completion message + $lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); + fwrite($lockfile, 'the installation is locked, delete this file to unlock it'); + fclose($lockfile); +} +?> -<!-- bottom layout here --> +<!-- bottom layout here --> \ No newline at end of file From 152020694b8106fb5d857ed104cc45731c9e9c21 Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 5 Oct 2024 15:06:36 -0600 Subject: [PATCH 470/514] fix: Comment --- public/installer/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/installer/index.php b/public/installer/index.php index 70dac50c6..403ca3cc5 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -1,5 +1,5 @@ <?php -// repport all error +// report all error ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); From 1192b3478c38bccc23b73ca79fb7389ffa6a87bc Mon Sep 17 00:00:00 2001 From: Zastinian <zastinian@gmail.com> Date: Sat, 5 Oct 2024 19:37:08 -0600 Subject: [PATCH 471/514] feat: Removing unnecessary comments --- public/installer/views/installation-complete.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/installer/views/installation-complete.php b/public/installer/views/installation-complete.php index 48605f71a..56b77fe4b 100644 --- a/public/installer/views/installation-complete.php +++ b/public/installer/views/installation-complete.php @@ -1,7 +1,6 @@ <!-- top layout here --> <?php -// Check if the installation is already locked if (!file_exists('../../install.lock')) { echo cardStart( $title = "Installation Complete!", @@ -17,7 +16,6 @@ class="mt-2 px-4 py-2 font-bold rounded-md bg-green-500/90 hover:bg-green-600 sh </a> <?php - // Create the lock file after displaying the completion message $lockfile = fopen('../../install.lock', 'w') or exit('Unable to open file!'); fwrite($lockfile, 'the installation is locked, delete this file to unlock it'); fclose($lockfile); From 9b01eea8f306219193f3b68c5131eefbc02c3cb4 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 8 Nov 2024 20:53:18 +0100 Subject: [PATCH 472/514] Fix Ticket Settings in migration || Fix credit purchase --- app/Listeners/UserPayment.php | 2 +- .../settings/2023_02_04_181156_create_ticket_settings.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Listeners/UserPayment.php b/app/Listeners/UserPayment.php index 476573e19..9e1066ef1 100644 --- a/app/Listeners/UserPayment.php +++ b/app/Listeners/UserPayment.php @@ -49,7 +49,7 @@ public function handle(PaymentEvent $event) $shopProduct = $event->shopProduct; // only update user if payment is paid - if ($event->payment->status != PaymentStatus::PAID->value) { + if ($event->payment->status != PaymentStatus::PAID) { return; } diff --git a/database/settings/2023_02_04_181156_create_ticket_settings.php b/database/settings/2023_02_04_181156_create_ticket_settings.php index b4cfd8f2f..f893d2270 100644 --- a/database/settings/2023_02_04_181156_create_ticket_settings.php +++ b/database/settings/2023_02_04_181156_create_ticket_settings.php @@ -10,8 +10,8 @@ public function up(): void $table_exists = DB::table('settings_old')->exists(); // Get the user-set configuration values from the old table. - $this->migrator->add('ticket.enabled', $table_exists ? $this->getOldValue('SETTINGS::TICKET:ENABLED') : 'true'); - $this->migrator->add('ticket.notify', $table_exists ? $this->getOldValue('SETTINGS::TICKET:NOTIFY') : 'all'); + $this->migrator->add('ticket.enabled', 'true'); + $this->migrator->add('ticket.notify', 'all'); } public function down(): void From de283330438ed9f2ce84f1f5c42f4345548d3e1c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 8 Nov 2024 21:09:25 +0100 Subject: [PATCH 473/514] Fix ticket creation --- app/Http/Controllers/TicketsController.php | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index de59bf883..c8c177b2e 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -11,6 +11,7 @@ use App\Notifications\Ticket\Admin\AdminCreateNotification; use App\Notifications\Ticket\Admin\AdminReplyNotification; use App\Notifications\Ticket\User\CreateNotification; +use App\Settings\GeneralSettings; use App\Settings\LocaleSettings; use App\Settings\PterodactylSettings; use App\Settings\TicketSettings; @@ -33,18 +34,20 @@ public function index(LocaleSettings $locale_settings, TicketSettings $ticketSet ]); } - public function store(Request $request, TicketSettings $ticket_settings) + public function store(Request $request, GeneralSettings $generalSettings) { - $this->validate( - $request, - [ - 'title' => 'required', - 'ticketcategory' => 'required', - 'priority' => 'required', - 'message' => 'required', - 'g-recaptcha-response' => ['required', 'recaptcha'], - ] - ); + $validateData = [ + 'title' => 'required', + 'ticketcategory' => 'required', + 'priority' => 'required', + 'message' => 'required', + + ]; + if ($generalSettings->recaptcha_enabled){ + $validateData['g-recaptcha-response'] = ['required', 'recaptcha']; + } + + $this->validate($request, $validateData); $ticket = new Ticket( [ 'title' => $request->input('title'), From f2b6147c40778dba3f02952fc57352002f63ff0f Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 8 Nov 2024 21:17:55 +0100 Subject: [PATCH 474/514] temp remove encryption since its bugged and we never had it before aswell --- .../2023_02_01_181334_create_pterodactyl_settings.php | 4 ++-- database/settings/2023_02_01_181453_create_mail_settings.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php index dbbb93b67..f2ed645a5 100644 --- a/database/settings/2023_02_01_181334_create_pterodactyl_settings.php +++ b/database/settings/2023_02_01_181334_create_pterodactyl_settings.php @@ -9,8 +9,8 @@ public function up(): void { $table_exists = DB::table('settings_old')->exists(); - $this->migrator->addEncrypted('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN', '') : env('PTERODACTYL_TOKEN', '')); - $this->migrator->addEncrypted('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN', '') : ''); + $this->migrator->add('pterodactyl.admin_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:TOKEN', '') : env('PTERODACTYL_TOKEN', '')); + $this->migrator->add('pterodactyl.user_token', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:ADMIN_USER_TOKEN', '') : ''); $this->migrator->add('pterodactyl.panel_url', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:URL', '') : env('PTERODACTYL_URL', '')); $this->migrator->add('pterodactyl.per_page_limit', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:PTERODACTYL:PER_PAGE_LIMIT', 200) : 200); } diff --git a/database/settings/2023_02_01_181453_create_mail_settings.php b/database/settings/2023_02_01_181453_create_mail_settings.php index b7d2c0d38..5dd15cab4 100644 --- a/database/settings/2023_02_01_181453_create_mail_settings.php +++ b/database/settings/2023_02_01_181453_create_mail_settings.php @@ -13,7 +13,7 @@ public function up(): void $this->migrator->add('mail.mail_host', $table_exists ? $this->getOldValue('SETTINGS::MAIL:HOST') : env('MAIL_HOST', 'localhost')); $this->migrator->add('mail.mail_port', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PORT') : env('MAIL_PORT', 25)); $this->migrator->add('mail.mail_username', $table_exists ? $this->getOldValue('SETTINGS::MAIL:USERNAME') : env('MAIL_USERNAME', '')); - $this->migrator->addEncrypted('mail.mail_password', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PASSWORD') : env('MAIL_PASSWORD', '')); + $this->migrator->add('mail.mail_password', $table_exists ? $this->getOldValue('SETTINGS::MAIL:PASSWORD') : env('MAIL_PASSWORD', '')); $this->migrator->add('mail.mail_encryption', $table_exists ? $this->getOldValue('SETTINGS::MAIL:ENCRYPTION') : env('MAIL_ENCRYPTION', 'tls')); $this->migrator->add('mail.mail_from_address', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_ADDRESS') : env('MAIL_FROM_ADDRESS', 'example@example.com')); $this->migrator->add('mail.mail_from_name', $table_exists ? $this->getOldValue('SETTINGS::MAIL:FROM_NAME') : env('APP_NAME', 'CtrlPanel.gg')); From 106b82bab9cf33bcbd554240257e8c010f337bd5 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 8 Nov 2024 21:26:46 +0100 Subject: [PATCH 475/514] encryption fix --- app/Settings/MailSettings.php | 3 ++- app/Settings/PterodactylSettings.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/Settings/MailSettings.php b/app/Settings/MailSettings.php index c3a9a367f..75bc106ff 100644 --- a/app/Settings/MailSettings.php +++ b/app/Settings/MailSettings.php @@ -19,6 +19,7 @@ public static function group(): string { return 'mail'; } + /* public static function encrypted(): array { @@ -26,7 +27,7 @@ public static function encrypted(): array 'mail_password', ]; } - +*/ public function setConfig() { try { diff --git a/app/Settings/PterodactylSettings.php b/app/Settings/PterodactylSettings.php index c96933867..fa158120f 100644 --- a/app/Settings/PterodactylSettings.php +++ b/app/Settings/PterodactylSettings.php @@ -15,7 +15,7 @@ public static function group(): string { return 'pterodactyl'; } - +/* public static function encrypted(): array { return [ @@ -23,7 +23,7 @@ public static function encrypted(): array 'user_token', ]; } - +*/ /** * Get url with ensured ending backslash * From 1d78861898feaf307afda5cc8d1ba9e1abad363f Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Fri, 8 Nov 2024 22:08:10 +0100 Subject: [PATCH 476/514] fix free products --- app/Http/Controllers/Admin/PaymentController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 0b11597ef..82efc3b5f 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Admin; +use App\Enums\PaymentStatus; use App\Events\CouponUsedEvent; use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; @@ -108,7 +109,7 @@ public function handleFreeProduct(ShopProduct $shopProduct) 'payment_id' => uniqid(), 'payment_method' => 'free', 'type' => $shopProduct->type, - 'status' => 'paid', + 'status' => PaymentStatus::PAID, 'amount' => $shopProduct->quantity, 'price' => $shopProduct->price - ($shopProduct->price * PartnerDiscount::getDiscount() / 100), 'tax_value' => $shopProduct->getTaxValue(), From fbe8d5ce02823e88c3a9f62858f1b4ac3cc18e7d Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 01:37:15 +0100 Subject: [PATCH 477/514] Permissions Cleanup and fixes as #952 --- .../Controllers/Admin/LegalController.php | 2 +- .../Controllers/Admin/ProductController.php | 3 +- .../Controllers/Admin/SettingsController.php | 4 + app/Http/Controllers/Admin/UserController.php | 64 ++++++++----- app/Http/Controllers/Controller.php | 17 ++++ config/permissions_web.php | 91 ++++++++++--------- 6 files changed, 112 insertions(+), 69 deletions(-) diff --git a/app/Http/Controllers/Admin/LegalController.php b/app/Http/Controllers/Admin/LegalController.php index 27c858fba..27f24aa4e 100644 --- a/app/Http/Controllers/Admin/LegalController.php +++ b/app/Http/Controllers/Admin/LegalController.php @@ -33,7 +33,7 @@ public function index() } public function update(Request $request){ - $this->checkPermission(self::READ_PERMISSION); + $this->checkPermission(self::WRITE_PERMISSION); $tos = $request->tos; $privacy = $request->privacy; diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index 6ddae3e38..d250307fe 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -20,7 +20,8 @@ class ProductController extends Controller { const READ_PERMISSION = "admin.products.read"; - const WRITE_PERMISSION = "admin.products.write"; + + const WRITE_PERMISSION = "admin.products.create"; const EDIT_PERMISSION = "admin.products.edit"; const DELETE_PERMISSION = "admin.products.delete"; /** diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 988603220..8cee2f582 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -15,6 +15,8 @@ class SettingsController extends Controller { + const ICON_PERMISSION = "admin.icons.edit"; + /** @@ -142,6 +144,8 @@ public function update(Request $request) public function updateIcons(Request $request) { + $this->checkPermission(self::ICON_PERMISSION); + $request->validate([ 'icon' => 'nullable|max:10000|mimes:jpg,png,jpeg', 'logo' => 'nullable|max:10000|mimes:jpg,png,jpeg', diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 57fc53cd2..82e670024 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -38,12 +38,15 @@ class UserController extends Controller const CHANGE_USERNAME_PERMISSION = "admin.users.write.username"; const CHANGE_PASSWORD_PERMISSION = "admin.users.write.password"; const CHANGE_ROLE_PERMISSION ="admin.users.write.role"; - const CHANGE_REFERAL_PERMISSION ="admin.users.write.referral"; + const CHANGE_REFERRAL_PERMISSION ="admin.users.write.referral"; const CHANGE_PTERO_PERMISSION = "admin.users.write.pterodactyl"; + + const CHANGE_SERVERLIMIT_PERMISSION = "admin.users.write.serverlimit"; const DELETE_PERMISSION = "admin.users.delete"; const NOTIFY_PERMISSION = "admin.users.notify"; const LOGIN_PERMISSION = "admin.users.login_as"; + private $pterodactyl; public function __construct(PterodactylSettings $ptero_settings) @@ -127,7 +130,9 @@ public function json(Request $request) */ public function edit(User $user, GeneralSettings $general_settings) { - $this->checkPermission(self::WRITE_PERMISSION); + $allConstants = (new \ReflectionClass(__CLASS__))->getConstants(); + $permissions = array_filter($allConstants, fn($key) => str_starts_with($key, 'admin.users.write')); + $this->checkAnyPermission($permissions); $roles = Role::all(); return view('admin.users.edit')->with([ @@ -169,34 +174,47 @@ public function update(Request $request, User $user) ]); } - if (!is_null($request->input('new_password')) && $this->can(self::CHANGE_PASSWORD_PERMISSION)) { + $dataArray = []; + + if ($this->canAny([self::CHANGE_USERNAME_PERMISSION, self::WRITE_PERMISSION]) && $request->filled('name')) { + $dataArray['name'] = $request->input('name'); + } + + if ($this->canAny([self::CHANGE_CREDITS_PERMISSION, self::WRITE_PERMISSION]) && $request->filled('credits')) { + $dataArray['credits'] = $request->input('credits'); + } + + if ($this->canAny([self::CHANGE_PTERO_PERMISSION, self::WRITE_PERMISSION]) && $request->filled('pterodactyl_id')) { + $dataArray['pterodactyl_id'] = $request->input('pterodactyl_id'); + } + + if ($this->canAny([self::CHANGE_REFERRAL_PERMISSION, self::WRITE_PERMISSION]) && $request->filled('referral_code')) { + $dataArray['referral_code'] = $request->input('referral_code'); + } + + if ($this->canAny([self::CHANGE_EMAIL_PERMISSION, self::WRITE_PERMISSION]) && $request->filled('email')) { + $dataArray['email'] = $request->input('email'); + } + + if ($this->canAny([self::CHANGE_SERVERLIMIT_PERMISSION, self::WRITE_PERMISSION]) && $request->filled('server_limit')) { + $dataArray['server_limit'] = $request->input('server_limit'); + } + + +// Update password separately with validation, if permission is granted + if (!is_null($request->input('new_password')) && $this->canAny([self::CHANGE_PASSWORD_PERMISSION, self::WRITE_PERMISSION])) { $request->validate([ 'new_password' => 'required|string|min:8', 'new_password_confirmation' => 'required|same:new_password', ]); - $user->update([ - 'password' => Hash::make($request->input('new_password')), - ]); + $dataArray['password'] = Hash::make($request->input('new_password')); } - // if($this->can(self::CHANGE_USERNAME_PERMISSION)){ - // $user->name = $request->name; - // } - // if($this->can(self::CHANGE_CREDITS_PERMISSION)){ - // $user->credits = $request->credits; - // } - // if($this->can(self::CHANGE_PTERO_PERMISSION)){ - // $user->pterodactyl_id = $request->pterodactyl_id; - // } - // if($this->can(self::CHANGE_REFERAL_PERMISSION)){ - // $user->referral_code = $request->referral_code; - // } - // if($this->can(self::CHANGE_EMAIL_PERMISSION)){ - // $user->email = $request->email; - // } - - $user->update($data); +// Only update with the collected data + if (!empty($dataArray)) { + $user->update($dataArray); + } event(new UserUpdateCreditsEvent($user)); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 49d020497..b6060d827 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -29,6 +29,16 @@ public function checkPermission(string $permission) } } + public function checkAnyPermission(iterable $permission) + { + /** @var User $user */ + $user = Auth::user(); + + if (!$user->canAny($permission)) { + abort(403, __('User does not have the right permissions.')); + } + } + /** * Check if user has permissions * @@ -42,4 +52,11 @@ public function can(string $permission): bool return $user->can($permission); } + public function canAny(iterable $permission): bool + { + /** @var User $user */ + $user = Auth::user(); + + return $user->canAny($permission); + } } diff --git a/config/permissions_web.php b/config/permissions_web.php index 1e67f65fc..38033129d 100644 --- a/config/permissions_web.php +++ b/config/permissions_web.php @@ -7,21 +7,21 @@ 'All Permissions' => '*', - 'View Roles' => 'admin.roles.read', + 'View Roles Backend' => 'admin.roles.read', 'Create Role' => 'admin.roles.create', 'Edit Role' => 'admin.roles.edit', 'Delete Role' => 'admin.roles.delete', - 'View Tickets' => 'admin.tickets.read', - 'Manage Ticket' => 'admin.tickets.write', - 'Receive Ticket Notifications' => 'admin.tickets.get_notification', + 'View Tickets Backend' => 'admin.tickets.read', + 'Manage Ticket Backend' => 'admin.tickets.write', + 'Receive new Ticket Notifications' => 'admin.tickets.get_notification', 'Create Ticket Category' => 'admin.tickets.category.read', - 'Manage Ticket Category' => 'admin.tickets.category.write', + 'Manage Ticket Category Backend' => 'admin.tickets.category.write', - 'View Blacklist Tickets' => 'admin.ticket_blacklist.read', - 'Manage Blacklist Tickets' => 'admin.ticket_blacklist.write', + 'View Ticket-Blacklist' => 'admin.ticket_blacklist.read', + 'Manage Ticket-Blacklist' => 'admin.ticket_blacklist.write', 'View Overview' => 'admin.overview.read', 'Overview Sync' => 'admin.overview.sync', @@ -29,54 +29,57 @@ 'View Api Keys' => 'admin.api.read', 'Manage Api Keys' => 'admin.api.write', - 'View Users' => 'admin.users.read', - 'Manage Users' => 'admin.users.write', + 'View User List' => 'admin.users.read', + 'Edit anything on User' => 'admin.users.write', 'Suspend Users' => 'admin.users.suspend', - 'Manage User Credits' => 'admin.users.write.credits', - 'Manage User Name' => 'admin.users.write.username', - 'Manage User Email' => 'admin.users.write.email', - 'Manage User Password' => 'admin.users.write.password', - 'Manage User Role' => 'admin.users.write.role', - 'Manage User Referral' => 'admin.users.write.referral', - 'Manage User Pterodactyl' => 'admin.users.write.pterodactyl', + 'Edit User Credits' => 'admin.users.write.credits', + 'Edit User Name' => 'admin.users.write.username', + 'Edit User Email' => 'admin.users.write.email', + 'Edit User Password' => 'admin.users.write.password', + 'Edit User Role' => 'admin.users.write.role', + 'Edit User Referral' => 'admin.users.write.referral', + 'Edit User Pterodactyl' => 'admin.users.write.pterodactyl', + 'Edit User Serverlimit' => 'admin.users.write.serverlimit', + + "Manage Icons" => "admin.icons.edit", 'Notify Users' => 'admin.users.notify', 'Login As User' => 'admin.users.login_as', 'Delete User' => 'admin.users.delete', - 'View Servers' => 'admin.servers.read', - 'Manage Servers' => 'admin.servers.write', - 'Suspend Server' => 'admin.servers.suspend', - 'Change Server Owner' => 'admin.servers.write.owner', - 'Manage Server Identifier' => 'admin.servers.write.identifier', - 'Create Server' => 'admin.servers.bypass_creation_enabled', - 'Delete Server' => 'admin.servers.delete', + 'View Server List' => 'admin.servers.read', + 'Manage all Servers' => 'admin.servers.write', + 'Suspend any Server' => 'admin.servers.suspend', + 'Change any Servers Owner' => 'admin.servers.write.owner', + 'Manage any Servers Identifier' => 'admin.servers.write.identifier', + 'Bypass Server-creation restriction ' => 'admin.servers.bypass_creation_enabled', + 'Delete any Servers' => 'admin.servers.delete', - 'View Products' => 'admin.products.read', + 'View Product List' => 'admin.products.read', 'Create Product' => 'admin.products.create', 'Edit Product' => 'admin.products.edit', 'Delete Product' => 'admin.products.delete', - 'View Store' => 'admin.store.read', - 'Manage Store' => 'admin.store.write', + 'View Store Backend' => 'admin.store.read', + 'Manage Store Backend' => 'admin.store.write', 'Disable Store' => 'admin.store.disable', - 'View Vouchers' => 'admin.voucher.read', - 'Manage Voucher' => 'admin.voucher.write', + 'View Vouchers Backend' => 'admin.voucher.read', + 'Manage Voucher Backend' => 'admin.voucher.write', - 'View Useful Links' => 'admin.useful_links.read', - 'Manage Useful Links' => 'admin.useful_links.write', + 'View Useful Links Backend' => 'admin.useful_links.read', + 'Manage Useful Links Backend' => 'admin.useful_links.write', - 'View Legal' => 'admin.legal.read', - 'Manage Legal' => 'admin.legal.write', + 'View Legal Backend' => 'admin.legal.read', + 'Manage Legal Backend' => 'admin.legal.write', - 'View Payments' => 'admin.payments.read', + 'View Payments Backend' => 'admin.payments.read', - 'View Partners' => 'admin.partners.read', - 'Manage Partners' => 'admin.partners.write', + 'View Partners Backend' => 'admin.partners.read', + 'Manage Partners Backend' => 'admin.partners.write', - 'View Coupons' => 'admin.coupons.read', - 'Manage Coupons' => 'admin.coupons.write', + 'View Coupons Backend' => 'admin.coupons.read', + 'Manage Coupons Backend' => 'admin.coupons.write', 'View Logs' => 'admin.logs.read', @@ -121,7 +124,7 @@ 'View Mercado Pago Settings' => 'settings.mercadopago.read', 'Manage Mercado Pago Settings' => 'settings.mercadopago.write', - + 'View Stripe Settings' => 'settings.stripe.read', 'Manage Stripe Settings' => 'settings.stripe.write', @@ -131,10 +134,10 @@ /* * Permissions for users */ - 'User Create Server' => 'user.server.create', - 'User Upgrade Server' => 'user.server.upgrade', - 'User Shop Buy' => 'user.shop.buy', - 'User View Tickets' => 'user.ticket.read', - 'User Manage Ticket' => 'user.ticket.write', - 'User View Referral' => 'user.referral', + 'Customer Create Server' => 'user.server.create', + 'Customer Upgrade Server' => 'user.server.upgrade', + 'Customer Shop Buy' => 'user.shop.buy', + 'Customer View Supportticket' => 'user.ticket.read', + 'Customer Write Supportticket' => 'user.ticket.write', + 'Customer View Referral' => 'user.referral', ]; From c0dbba0344251f7173e4b636fb937222bb393bae Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 01:55:13 +0100 Subject: [PATCH 478/514] Overhaul the Permissions on Controllers --- app/Http/Controllers/Admin/ApplicationApiController.php | 2 +- app/Http/Controllers/Admin/CouponController.php | 2 +- app/Http/Controllers/Admin/LegalController.php | 2 +- app/Http/Controllers/Admin/OverViewController.php | 2 +- app/Http/Controllers/Admin/PartnerController.php | 2 +- app/Http/Controllers/Admin/ProductController.php | 5 +++-- app/Http/Controllers/Admin/RoleController.php | 3 ++- app/Http/Controllers/Admin/ServerController.php | 7 +++++-- app/Http/Controllers/Admin/ShopProductController.php | 2 +- app/Http/Controllers/Admin/TicketCategoryController.php | 2 +- app/Http/Controllers/Admin/TicketsController.php | 6 +++--- app/Http/Controllers/Admin/UsefulLinkController.php | 2 +- app/Http/Controllers/Admin/UserController.php | 5 ++++- app/Http/Controllers/Admin/VoucherController.php | 2 +- 14 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/Admin/ApplicationApiController.php b/app/Http/Controllers/Admin/ApplicationApiController.php index f6c00bbd8..f53a4fb42 100644 --- a/app/Http/Controllers/Admin/ApplicationApiController.php +++ b/app/Http/Controllers/Admin/ApplicationApiController.php @@ -25,7 +25,7 @@ class ApplicationApiController extends Controller */ public function index(LocaleSettings $locale_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION,self::WRITE_PERMISSION]); return view('admin.api.index', [ 'locale_datatables' => $locale_settings->datatables diff --git a/app/Http/Controllers/Admin/CouponController.php b/app/Http/Controllers/Admin/CouponController.php index 71caaf31c..d7d796b20 100644 --- a/app/Http/Controllers/Admin/CouponController.php +++ b/app/Http/Controllers/Admin/CouponController.php @@ -23,7 +23,7 @@ class CouponController extends Controller */ public function index(LocaleSettings $localeSettings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::WRITE_PERMISSION,self::READ_PERMISSION]); return view('admin.coupons.index', [ 'locale_datatables' => $localeSettings->datatables diff --git a/app/Http/Controllers/Admin/LegalController.php b/app/Http/Controllers/Admin/LegalController.php index 27f24aa4e..2f101e356 100644 --- a/app/Http/Controllers/Admin/LegalController.php +++ b/app/Http/Controllers/Admin/LegalController.php @@ -19,7 +19,7 @@ class LegalController extends Controller */ public function index() { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION,self::WRITE_PERMISSION]); $tos = File::get(Theme::path($path = 'views', "default") . '/information/tos-content.blade.php'); $privacy = File::get(Theme::path($path = 'views', "default") . '/information/privacy-content.blade.php'); diff --git a/app/Http/Controllers/Admin/OverViewController.php b/app/Http/Controllers/Admin/OverViewController.php index 2b0a4f69d..6d9ebab80 100644 --- a/app/Http/Controllers/Admin/OverViewController.php +++ b/app/Http/Controllers/Admin/OverViewController.php @@ -32,7 +32,7 @@ public function __construct(PterodactylSettings $ptero_settings) public function index(GeneralSettings $general_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION,self::SYNC_PERMISSION]); //Get counters $counters = collect(); diff --git a/app/Http/Controllers/Admin/PartnerController.php b/app/Http/Controllers/Admin/PartnerController.php index 8b65eec3f..3b641c5f2 100644 --- a/app/Http/Controllers/Admin/PartnerController.php +++ b/app/Http/Controllers/Admin/PartnerController.php @@ -15,7 +15,7 @@ class PartnerController extends Controller const WRITE_PERMISSION = "admin.partners.write"; public function index(LocaleSettings $locale_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::WRITE_PERMISSION,self::READ_PERMISSION]); return view('admin.partners.index', [ 'locale_datatables' => $locale_settings->datatables diff --git a/app/Http/Controllers/Admin/ProductController.php b/app/Http/Controllers/Admin/ProductController.php index d250307fe..2778dd6f5 100644 --- a/app/Http/Controllers/Admin/ProductController.php +++ b/app/Http/Controllers/Admin/ProductController.php @@ -31,7 +31,8 @@ class ProductController extends Controller */ public function index(LocaleSettings $locale_settings) { - $this->checkPermission(self::READ_PERMISSION); + $allConstants = (new \ReflectionClass(__CLASS__))->getConstants(); + $this->checkAnyPermission($allConstants); return view('admin.products.index', [ 'locale_datatables' => $locale_settings->datatables @@ -113,7 +114,7 @@ public function store(Request $request) */ public function show(Product $product, UserSettings $user_settings, GeneralSettings $general_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION,self::WRITE_PERMISSION]); return view('admin.products.show', [ 'product' => $product, diff --git a/app/Http/Controllers/Admin/RoleController.php b/app/Http/Controllers/Admin/RoleController.php index 3ce5ad5bb..0d09c5d5f 100644 --- a/app/Http/Controllers/Admin/RoleController.php +++ b/app/Http/Controllers/Admin/RoleController.php @@ -31,7 +31,8 @@ class RoleController extends Controller public function index(Request $request) { - $this->checkPermission(self::READ_PERMISSION); + $allConstants = (new \ReflectionClass(__CLASS__))->getConstants(); + $this->checkAnyPermission($allConstants); //datatables if ($request->ajax()) { diff --git a/app/Http/Controllers/Admin/ServerController.php b/app/Http/Controllers/Admin/ServerController.php index 087dbf83e..4d83d39c8 100644 --- a/app/Http/Controllers/Admin/ServerController.php +++ b/app/Http/Controllers/Admin/ServerController.php @@ -42,7 +42,8 @@ public function __construct(PterodactylSettings $ptero_settings) */ public function index(LocaleSettings $locale_settings) { - $this->checkPermission(self::READ_PERMISSION); + $allConstants = (new \ReflectionClass(__CLASS__))->getConstants(); + $this->checkAnyPermission($allConstants); return view('admin.servers.index', [ 'locale_datatables' => $locale_settings->datatables @@ -57,7 +58,9 @@ public function index(LocaleSettings $locale_settings) */ public function edit(Server $server) { - $this->checkPermission(self::WRITE_PERMISSION); + $allConstants = (new \ReflectionClass(__CLASS__))->getConstants(); + $permissions = array_filter($allConstants, fn($key) => str_starts_with($key, 'admin.servers.write')); + $this->checkAnyPermission($permissions); // get all users from the database $users = User::all(); diff --git a/app/Http/Controllers/Admin/ShopProductController.php b/app/Http/Controllers/Admin/ShopProductController.php index db07916a8..4ed07ba3c 100644 --- a/app/Http/Controllers/Admin/ShopProductController.php +++ b/app/Http/Controllers/Admin/ShopProductController.php @@ -28,7 +28,7 @@ class ShopProductController extends Controller */ public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION, self::WRITE_PERMISSION]); $isStoreEnabled = $general_settings->store_enabled; diff --git a/app/Http/Controllers/Admin/TicketCategoryController.php b/app/Http/Controllers/Admin/TicketCategoryController.php index 3d4d8e879..b0ae8e21b 100644 --- a/app/Http/Controllers/Admin/TicketCategoryController.php +++ b/app/Http/Controllers/Admin/TicketCategoryController.php @@ -19,7 +19,7 @@ class TicketCategoryController extends Controller */ public function index() { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION, self::WRITE_PERMISSION]); $categories = TicketCategory::all(); return view('admin.ticket.category')->with("categories",$categories); diff --git a/app/Http/Controllers/Admin/TicketsController.php b/app/Http/Controllers/Admin/TicketsController.php index 025c58de3..ec1d6578d 100644 --- a/app/Http/Controllers/Admin/TicketsController.php +++ b/app/Http/Controllers/Admin/TicketsController.php @@ -25,7 +25,7 @@ class TicketsController extends Controller const BLACKLIST_WRITE_PERMISSION ='admin.ticket_blacklist.write'; public function index(LocaleSettings $locale_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION, self::WRITE_PERMISSION]); return view('admin.ticket.index', [ 'tickets' => Ticket::orderBy('id', 'desc')->paginate(10), @@ -36,7 +36,7 @@ public function index(LocaleSettings $locale_settings) public function show($ticket_id, PterodactylSettings $ptero_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION, self::WRITE_PERMISSION]); try { $ticket = Ticket::where('ticket_id', $ticket_id)->firstOrFail(); } catch (Exception $e) @@ -186,7 +186,7 @@ public function dataTable() public function blacklist(LocaleSettings $locale_settings) { - $this->checkPermission(self::BLACKLIST_READ_PERMISSION); + $this->checkAnyPermission([self::BLACKLIST_READ_PERMISSION, self::BLACKLIST_WRITE_PERMISSION]); return view('admin.ticket.blacklist', [ 'locale_datatables' => $locale_settings->datatables diff --git a/app/Http/Controllers/Admin/UsefulLinkController.php b/app/Http/Controllers/Admin/UsefulLinkController.php index 17f8774b2..55a62d684 100644 --- a/app/Http/Controllers/Admin/UsefulLinkController.php +++ b/app/Http/Controllers/Admin/UsefulLinkController.php @@ -24,7 +24,7 @@ class UsefulLinkController extends Controller */ public function index(LocaleSettings $locale_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION, self::WRITE_PERMISSION]); return view('admin.usefullinks.index', [ 'locale_datatables' => $locale_settings->datatables ]); diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 82e670024..3cc7709f9 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -62,7 +62,10 @@ public function __construct(PterodactylSettings $ptero_settings) */ public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings) { - $this->checkPermission(self::READ_PERMISSION); + $allConstants = (new \ReflectionClass(__CLASS__))->getConstants(); + $this->checkAnyPermission($allConstants); + + //$this->checkPermission(self::READ_PERMISSION); return view('admin.users.index', [ 'locale_datatables' => $locale_settings->datatables, diff --git a/app/Http/Controllers/Admin/VoucherController.php b/app/Http/Controllers/Admin/VoucherController.php index b42d5094c..4e65e7d54 100644 --- a/app/Http/Controllers/Admin/VoucherController.php +++ b/app/Http/Controllers/Admin/VoucherController.php @@ -28,7 +28,7 @@ class VoucherController extends Controller */ public function index(LocaleSettings $locale_settings, GeneralSettings $general_settings) { - $this->checkPermission(self::READ_PERMISSION); + $this->checkAnyPermission([self::READ_PERMISSION, self::WRITE_PERMISSION]); return view('admin.vouchers.index', [ 'locale_datatables' => $locale_settings->datatables, From 4b96e9f52f9cb1e64864a0ab5a6edcc4fd3b163c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 02:37:41 +0100 Subject: [PATCH 479/514] Fix sales Tax || Fix Icons edit Permission --- app/Models/ShopProduct.php | 4 +++- app/Settings/GeneralSettings.php | 17 +++++++---------- ...023_02_01_164731_create_general_settings.php | 8 ++++++++ .../views/admin/settings/index.blade.php | 2 ++ 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/app/Models/ShopProduct.php b/app/Models/ShopProduct.php index 8e8a2260b..4515f5d79 100644 --- a/app/Models/ShopProduct.php +++ b/app/Models/ShopProduct.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Settings\GeneralSettings; use Hidehalo\Nanoid\Client; use Illuminate\Database\Eloquent\Model; use NumberFormatter; @@ -73,7 +74,8 @@ public function formatToCurrency($value, $locale = 'en_US') */ public function getTaxPercent() { - $tax = config('SETTINGS::PAYMENTS:SALES_TAX'); + $generalSettings = new GeneralSettings(); + $tax = $generalSettings->sales_tax; return $tax < 0 ? 0 : $tax; } diff --git a/app/Settings/GeneralSettings.php b/app/Settings/GeneralSettings.php index 23c0c0f76..3c3310a88 100644 --- a/app/Settings/GeneralSettings.php +++ b/app/Settings/GeneralSettings.php @@ -7,6 +7,7 @@ class GeneralSettings extends Settings { public bool $store_enabled; + public ?int $sales_tax; public string $credits_display_name; public bool $recaptcha_enabled; public ?string $recaptcha_site_key; @@ -34,6 +35,7 @@ public static function getValidations() { return [ 'store_enabled' => 'nullable|string', + 'sales_tax' => 'nullable|number', 'credits_display_name' => 'required|string', 'recaptcha_enabled' => 'nullable|string', 'recaptcha_site_key' => 'nullable|string', @@ -60,21 +62,16 @@ public static function getOptionInputData() 'label' => 'Enable Store', 'description' => 'Enable the store for users to purchase credits.' ], + 'sales_tax' => [ + 'type' => 'number', + 'label' => 'Sales Tax in %', + 'description' => 'Your countrys sales tax in %' + ], 'credits_display_name' => [ 'type' => 'string', 'label' => 'Credits Display Name', 'description' => 'The name of the currency used.' ], - 'initial_user_credits' => [ - 'type' => 'number', - 'label' => 'Initial User Credits', - 'description' => 'The amount of credits a user gets when they register.' - ], - 'initial_server_limit' => [ - 'type' => 'number', - 'label' => 'Initial Server Limit', - 'description' => 'The amount of servers a user can create when they register.' - ], 'recaptcha_enabled' => [ 'type' => 'boolean', 'label' => 'Enable reCAPTCHA', diff --git a/database/settings/2023_02_01_164731_create_general_settings.php b/database/settings/2023_02_01_164731_create_general_settings.php index 06c876c5b..e799e4406 100644 --- a/database/settings/2023_02_01_164731_create_general_settings.php +++ b/database/settings/2023_02_01_164731_create_general_settings.php @@ -11,6 +11,7 @@ public function up(): void // Get the user-set configuration values from the old table. $this->migrator->add('general.store_enabled', true); + $this->migrator->add('general.sales_tax', $table_exists ? $this->getOldValue('SETTINGS::PAYMENTS:SALES_TAX', '0') : '0'); $this->migrator->add('general.credits_display_name', $table_exists ? $this->getOldValue('SETTINGS::SYSTEM:CREDITS_DISPLAY_NAME', 'Credits') : 'Credits'); $this->migrator->add('general.recaptcha_site_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SITE_KEY") : env('RECAPTCHA_SITE_KEY', '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI')); $this->migrator->add('general.recaptcha_secret_key', $table_exists ? $this->getOldValue("SETTINGS::RECAPTCHA:SECRET_KEY") : env('RECAPTCHA_SECRET_KEY', '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe')); @@ -31,6 +32,12 @@ public function down(): void 'type' => 'string', 'description' => 'The name of the credits on the panel.' ], + [ + 'key' => 'SETTINGS::PAYMENTS:SALES_TAX', + 'value' => $this->getNewValue('sales_tax', 'general'), + 'type' => 'string', + 'description' => 'Sales tax in %.' + ], [ 'key' => 'SETTINGS::SYSTEM:ALERT_ENABLED', 'value' => $this->getNewValue('alert_enabled', 'general'), @@ -83,6 +90,7 @@ public function down(): void ]); try { $this->migrator->delete('general.store_enabled'); + $this->migrator->delete('general.sales_tax'); $this->migrator->delete('general.credits_display_name'); $this->migrator->delete('general.recaptcha_site_key'); $this->migrator->delete('general.recaptcha_secret_key'); diff --git a/themes/default/views/admin/settings/index.blade.php b/themes/default/views/admin/settings/index.blade.php index 4d07a9e31..3789727d6 100644 --- a/themes/default/views/admin/settings/index.blade.php +++ b/themes/default/views/admin/settings/index.blade.php @@ -47,6 +47,7 @@ <nav class="mt-1"> <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="tablist" data-accordion="false"> + @can("admin.icons.edit") <li class="nav-item border-bottom-0"> <a href="#icons" class="nav-link" data-toggle="pill" role="tab"> <i class="nav-icon fas fa-image"></i> @@ -55,6 +56,7 @@ </p> </a> </li> + @endcan @foreach ($settings as $category => $options) @if (!str_contains($options['settings_class'], 'Extension')) @canany(['settings.' . strtolower($category) . '.read', 'settings.' . From 00b0615a8ae894f33e318e47d8765188da3f7b67 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 11:32:38 +0100 Subject: [PATCH 480/514] better settings descriptions --- app/Settings/LocaleSettings.php | 2 +- app/Settings/PterodactylSettings.php | 2 +- app/Settings/ReferralSettings.php | 2 +- app/Settings/WebsiteSettings.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Settings/LocaleSettings.php b/app/Settings/LocaleSettings.php index 4e38cbe2f..32006f572 100644 --- a/app/Settings/LocaleSettings.php +++ b/app/Settings/LocaleSettings.php @@ -67,7 +67,7 @@ public static function getOptionInputData() 'dynamic' => [ 'label' => 'Dynamic Locale', 'type' => 'boolean', - 'description' => 'Whether to use the dynamic locale.', + 'description' => 'Whether to choose the language automatically based on the Geolocation of the client.', ], ]; } diff --git a/app/Settings/PterodactylSettings.php b/app/Settings/PterodactylSettings.php index fa158120f..6ece4a975 100644 --- a/app/Settings/PterodactylSettings.php +++ b/app/Settings/PterodactylSettings.php @@ -75,7 +75,7 @@ public static function getOptionInputData() 'per_page_limit' => [ 'label' => 'Per Page Limit', 'type' => 'number', - 'description' => 'The number of servers to show per page.', + 'description' => 'The number of servers to show per page for the API call. Only change this when needed.', ], ]; } diff --git a/app/Settings/ReferralSettings.php b/app/Settings/ReferralSettings.php index b08923fde..de16ff89d 100644 --- a/app/Settings/ReferralSettings.php +++ b/app/Settings/ReferralSettings.php @@ -54,7 +54,7 @@ public static function getOptionInputData() 'reward' => [ 'label' => 'Reward', 'type' => 'number', - 'description' => 'Reward for the referrer.', + 'description' => 'Reward in credits for the referrer.', ], 'mode' => [ 'label' => 'Mode', diff --git a/app/Settings/WebsiteSettings.php b/app/Settings/WebsiteSettings.php index e6607c210..f768b0212 100644 --- a/app/Settings/WebsiteSettings.php +++ b/app/Settings/WebsiteSettings.php @@ -91,7 +91,7 @@ public static function getOptionInputData() 'seo_description' => [ 'label' => 'SEO Description', 'type' => 'string', - 'description' => 'The description of the website.', + 'description' => 'The description of the website shown in the Meta tag (for example when sending the URL in discord).', ], 'enable_login_logo' => [ 'label' => 'Enable Login Logo', From 60489a00d6494fca8fba41d17cd8abd92f680b02 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 11:40:07 +0100 Subject: [PATCH 481/514] fix sales tax validator --- app/Settings/GeneralSettings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Settings/GeneralSettings.php b/app/Settings/GeneralSettings.php index 3c3310a88..1b8e26944 100644 --- a/app/Settings/GeneralSettings.php +++ b/app/Settings/GeneralSettings.php @@ -35,7 +35,7 @@ public static function getValidations() { return [ 'store_enabled' => 'nullable|string', - 'sales_tax' => 'nullable|number', + 'sales_tax' => 'nullable|numeric', 'credits_display_name' => 'required|string', 'recaptcha_enabled' => 'nullable|string', 'recaptcha_site_key' => 'nullable|string', From 43e12c3dd6341aa0bcb066e0591cb2ac426ca68b Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 18:47:34 +0100 Subject: [PATCH 482/514] remove unused classes --- routes/web.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/routes/web.php b/routes/web.php index 7d56e5f04..f0f6cd61b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,10 +1,5 @@ <?php -use App\Classes\Settings\Invoices; -use App\Classes\Settings\Language; -use App\Classes\Settings\Misc; -use App\Classes\Settings\Payments; -use App\Classes\Settings\System; use App\Http\Controllers\Admin\ActivityLogController; use App\Http\Controllers\Admin\ApplicationApiController; use App\Http\Controllers\Admin\InvoiceController; From 393cbde662c7e54829e296eb5815794490d925c7 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 19:05:01 +0100 Subject: [PATCH 483/514] add validation to tickets --- app/Http/Controllers/TicketsController.php | 14 +++++++------- .../2024_06_25_133205_add_discord_settings.php | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index c8c177b2e..56a27c0f5 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -36,14 +36,14 @@ public function index(LocaleSettings $locale_settings, TicketSettings $ticketSet public function store(Request $request, GeneralSettings $generalSettings) { - $validateData = [ - 'title' => 'required', - 'ticketcategory' => 'required', - 'priority' => 'required', - 'message' => 'required', - + $validateData = [ + 'title' => 'required|string|max:255', + 'ticketcategory' => 'required|numeric', + 'priority' => ['required', 'in:Low,Medium,High'], + 'message' => 'required|string|min:10|max:2000', ]; - if ($generalSettings->recaptcha_enabled){ + + if ($generalSettings->recaptcha_enabled) { $validateData['g-recaptcha-response'] = ['required', 'recaptcha']; } diff --git a/database/settings/2024_06_25_133205_add_discord_settings.php b/database/settings/2024_06_25_133205_add_discord_settings.php index b73729f7d..1ed0ae241 100644 --- a/database/settings/2024_06_25_133205_add_discord_settings.php +++ b/database/settings/2024_06_25_133205_add_discord_settings.php @@ -6,8 +6,8 @@ class AddDiscordSettings extends SettingsMigration { public function up(): void { - $this->migrator->add('discord.role_on_purchase', null); - $this->migrator->add('discord.role_id_on_purchase', null); + $this->migrator->add('discord.role_on_purchase', false); + $this->migrator->add('discord.role_id_on_purchase', ""); } public function down(): void From d3b0d5a255a1101e8c02b75cb627627efb0349fb Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 20:12:43 +0100 Subject: [PATCH 484/514] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c5dcfb90..20df67211 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Follow the [documentation](https://ctrlpanel.gg/docs/intro) to know how to insta ### MarketPlace -If you need more functionality, check out [Marketplace](https://market.ctrlpanel.gg/resources/). +If you need more functionality, check out [Marketplace](https://market.ctrlpanel.gg/). ## 🆙 How to Update From ccb46f140a554c8aa3dce4f10f83917928619b37 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 21:00:15 +0100 Subject: [PATCH 485/514] fix role on discord select --- app/Settings/DiscordSettings.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/Settings/DiscordSettings.php b/app/Settings/DiscordSettings.php index 69284e01d..40cc21458 100644 --- a/app/Settings/DiscordSettings.php +++ b/app/Settings/DiscordSettings.php @@ -77,9 +77,14 @@ public static function getOptionInputData() 'type' => 'string', 'description' => 'The role ID for your Discord server.', ], + 'role_on_purchase' => [ 'label' => 'Role on Purchase', - 'type' => 'boolean', + 'type' => 'select', + 'options' => [ + '0' => 'Disabled', + '1' => 'Enabled' + ], 'description' => 'Give the user a role on purchase (removes when user has no active servers)', ], 'role_id_on_purchase' => [ From 8ed6b52913c32f8cf712b292080de87029ddfdd4 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 23:23:34 +0100 Subject: [PATCH 486/514] fix discord linking finally --- .../Controllers/Auth/SocialiteController.php | 8 ++--- app/Models/DiscordUser.php | 7 +++-- app/Providers/AppServiceProvider.php | 7 +++++ app/Providers/DiscordServiceProvider.php | 30 +++++++++++++++++++ app/Settings/DiscordSettings.php | 4 +-- config/app.php | 1 + config/services.php | 10 +------ 7 files changed, 49 insertions(+), 18 deletions(-) create mode 100644 app/Providers/DiscordServiceProvider.php diff --git a/app/Http/Controllers/Auth/SocialiteController.php b/app/Http/Controllers/Auth/SocialiteController.php index 02904c51f..af0ad213e 100644 --- a/app/Http/Controllers/Auth/SocialiteController.php +++ b/app/Http/Controllers/Auth/SocialiteController.php @@ -17,9 +17,9 @@ public function redirect(DiscordSettings $discord_settings) { $scopes = !empty($discord_settings->bot_token) && !empty($discord_settings->guild_id) ? ['guilds.join'] : []; - return Socialite::driver('discord') + return ( Socialite::driver('discord') ->scopes($scopes) - ->redirect(); + ->redirect()); } public function callback(DiscordSettings $discord_settings, UserSettings $user_settings) @@ -70,11 +70,11 @@ public function callback(DiscordSettings $discord_settings, UserSettings $user_s "https://discord.com/api/guilds/{$guildId}/members/{$discord->id}", ['access_token' => $discord->token] ); - + $discordUser = $user->discordUser; //give user a role in the discord server if (! empty($roleId)) { // Function addOrRemoveRole is defined in app/Models/DiscordUser.php - $user->discordUser->addOrRemoveRole('add', $roleId); + $discordUser->addOrRemoveRole('add', $roleId); } } diff --git a/app/Models/DiscordUser.php b/app/Models/DiscordUser.php index 900e8c625..fd2940c1d 100644 --- a/app/Models/DiscordUser.php +++ b/app/Models/DiscordUser.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Settings\DiscordSettings; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -53,7 +54,7 @@ public function getAvatar() */ public function addOrRemoveRole(string $action, string $role_id): mixed { - $discordSettings = app('discord_settings'); + $discordSettings = app(DiscordSettings::class); return match ($action) { 'add' => Http::withHeaders( [ @@ -63,7 +64,7 @@ public function addOrRemoveRole(string $action, string $role_id): mixed ] )->put( "https://discord.com/api/guilds/{$discordSettings->guild_id}/members/{$this->id}/roles/{$discordSettings->role_id}", - ['access_token' => $discordSettings->token] + ['access_token' => $discordSettings->bot_token] ), 'remove' => Http::withHeaders( [ @@ -73,7 +74,7 @@ public function addOrRemoveRole(string $action, string $role_id): mixed ] )->remove( "https://discord.com/api/guilds/{$discordSettings->guild_id}/members/{$this->id}/roles/{$discordSettings->role_id}", - ['access_token' => $discordSettings->token] + ['access_token' => $discordSettings->bot_token] ), default => null, }; diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5e1f05d05..cfef99b9d 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -3,10 +3,12 @@ namespace App\Providers; use App\Models\UsefulLink; +use App\Settings\DiscordSettings; use App\Settings\GeneralSettings; use App\Settings\MailSettings; use Exception; use Illuminate\Pagination\Paginator; +use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\URL; @@ -37,6 +39,10 @@ public function boot() Paginator::useBootstrap(); Schema::defaultStringLength(191); + Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { + $event->extendSocialite('discord', \SocialiteProviders\Discord\Provider::class); + }); + Validator::extend('multiple_date_format', function ($attribute, $value, $parameters, $validator) { $ok = true; $result = []; @@ -103,6 +109,7 @@ public function boot() $settings = $this->app->make(MailSettings::class); $settings->setConfig(); + } catch (Exception $e) { Log::error("Couldnt load Settings. Probably the installation is not completet. " . $e); } diff --git a/app/Providers/DiscordServiceProvider.php b/app/Providers/DiscordServiceProvider.php new file mode 100644 index 000000000..30d0dd0b7 --- /dev/null +++ b/app/Providers/DiscordServiceProvider.php @@ -0,0 +1,30 @@ +<?php + +namespace App\Providers; + +use Illuminate\Support\Facades\Config; +use Illuminate\Support\ServiceProvider; +use App\Settings\DiscordSettings; + +class DiscordServiceProvider extends ServiceProvider +{ + public function register() + { + // No need to register anything + } + + public function boot() + { + // Retrieve Discord settings from the Spatie settings class + $discordSettings = app(DiscordSettings::class); + + // Inject the settings into the config + Config::set('services.discord.client_id', $discordSettings->client_id); + Config::set('services.discord.client_secret', $discordSettings->client_secret); + Config::set('services.discord.redirect', env('APP_URL', 'http://localhost') . '/auth/callback'); + + // optional + Config::set('services.discord.allow_gif_avatars', (bool)env('DISCORD_AVATAR_GIF', true)); + Config::set('services.discord.avatar_default_extension', env('DISCORD_EXTENSION_DEFAULT', 'jpg')); + } +} diff --git a/app/Settings/DiscordSettings.php b/app/Settings/DiscordSettings.php index 40cc21458..c2ad55395 100644 --- a/app/Settings/DiscordSettings.php +++ b/app/Settings/DiscordSettings.php @@ -75,7 +75,7 @@ public static function getOptionInputData() 'role_id' => [ 'label' => 'Role ID', 'type' => 'string', - 'description' => 'The role ID for your Discord server.', + 'description' => 'Role to give users when linking their discord Account.', ], 'role_on_purchase' => [ @@ -85,7 +85,7 @@ public static function getOptionInputData() '0' => 'Disabled', '1' => 'Enabled' ], - 'description' => 'Give the user a role on purchase (removes when user has no active servers)', + 'description' => 'Give the user a role on purchase of Credits/Servers (removes when user has no active servers)', ], 'role_id_on_purchase' => [ 'label' => 'Role ID on Purchase', diff --git a/config/app.php b/config/app.php index 9d82ff72b..bc976a3b5 100644 --- a/config/app.php +++ b/config/app.php @@ -212,6 +212,7 @@ Yajra\DataTables\DataTablesServiceProvider::class, KKomelin\TranslatableStringExporter\Providers\ExporterServiceProvider::class, Biscolab\ReCaptcha\ReCaptchaServiceProvider::class, + App\Providers\DiscordServiceProvider::class, ], /* diff --git a/config/services.php b/config/services.php index aedb7007c..4ca9034f5 100644 --- a/config/services.php +++ b/config/services.php @@ -1,5 +1,6 @@ <?php + return [ /* @@ -31,14 +32,5 @@ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], - 'discord' => [ - 'client_id' => env('DISCORD_CLIENT_ID'), - 'client_secret' => env('DISCORD_CLIENT_SECRET'), - 'redirect' => env('APP_URL', 'http://localhost').'/auth/callback', - - // optional - 'allow_gif_avatars' => (bool) env('DISCORD_AVATAR_GIF', true), - 'avatar_default_extension' => env('DISCORD_EXTENSION_DEFAULT', 'jpg'), // only pick from jpg, png, webp - ], ]; From 81d55e4b97424e55ab24a31d413e8753c77bd30d Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Sun, 10 Nov 2024 23:34:03 +0100 Subject: [PATCH 487/514] add user to discord role after purchase fix --- app/Listeners/UserPayment.php | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/app/Listeners/UserPayment.php b/app/Listeners/UserPayment.php index 9e1066ef1..cb75f589f 100644 --- a/app/Listeners/UserPayment.php +++ b/app/Listeners/UserPayment.php @@ -5,6 +5,7 @@ use App\Enums\PaymentStatus; use App\Events\PaymentEvent; use App\Models\User; +use App\Settings\DiscordSettings; use Illuminate\Support\Facades\DB; use App\Models\PartnerDiscount; use App\Settings\GeneralSettings; @@ -23,18 +24,28 @@ class UserPayment private $credits_display_name; + private $role_id_on_purchase; + + private $role_on_purchase; + + private $bot_token; + /** * Create the event listener. * * @return void */ - public function __construct(UserSettings $user_settings, ReferralSettings $referral_settings, GeneralSettings $general_settings) + public function __construct(UserSettings $user_settings, ReferralSettings $referral_settings, GeneralSettings $general_settings, DiscordSettings $discord_settings) { $this->server_limit_after_irl_purchase = $user_settings->server_limit_after_irl_purchase; $this->referral_mode = $referral_settings->mode; $this->referral_percentage = $referral_settings->percentage; $this->referral_always_give_commission = $referral_settings->always_give_commission; $this->credits_display_name = $general_settings->credits_display_name; + $this->role_id_on_purchase = $discord_settings->role_id_on_purchase; + $this->role_on_purchase = $discord_settings->role_on_purchase; + $this->bot_token = $discord_settings->bot_token; + } /** @@ -58,6 +69,7 @@ public function handle(PaymentEvent $event) $user->update(['server_limit' => $this->server_limit_after_irl_purchase]); } + //update User with bought item if ($shopProduct->type == "Credits") { $user->increment('credits', $shopProduct->quantity); @@ -99,6 +111,18 @@ public function handle(PaymentEvent $event) } } + //set discord role + if(!empty($this->bot_token) && $this->role_on_purchase && !empty($this->role_id_on_purchase)) { + $discordUser = $user->discordUser; + $discordUser->addOrRemoveRole('add', $this->role_id_on_purchase); + + activity() + ->performedOn($user) + ->causedBy($user) + ->log('was added to role ' . $this->role_id_on_purchase . " on Discord"); + } + + // LOGS PAYMENT IN THE ACTIVITY LOG activity() ->performedOn($user) From 03c2cac26e4aae95138572993ffdf9275528d31e Mon Sep 17 00:00:00 2001 From: Dennis <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 08:55:11 +0100 Subject: [PATCH 488/514] Update SettingsController.php - Remove discord settings --- app/Http/Controllers/Admin/SettingsController.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index e50ea7b19..3725801ff 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -9,7 +9,6 @@ use Illuminate\Contracts\View\View; use Illuminate\Http\Response; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Validator; use Qirolab\Theme\Theme; @@ -123,19 +122,6 @@ public function update(Request $request) $rp = new \ReflectionProperty($settingsClass, $key); $rpType = $rp->getType(); - // Check if the settingsclass is a DiscordSettings class - if($settings_class == 'App\Settings\DiscordSettings') { - if($key === 'client_id' || $key === 'client_secret') { - - $env = file_get_contents(base_path('.env')); - $env = preg_replace('/DISCORD_CLIENT_ID=(.*)/', 'DISCORD_CLIENT_ID=' . $request->input('client_id'), $env); - $env = preg_replace('/DISCORD_CLIENT_SECRET=(.*)/', 'DISCORD_CLIENT_SECRET=' . $request->input('client_secret'), $env); - file_put_contents(base_path('.env'), $env); - - Artisan::call('config:clear'); - } - } - if ($rpType == 'bool') { $settingsClass->$key = $request->has($key); continue; From 23bd41d7fff4ee921cec424702ddc725baa778f8 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 10:04:43 +0100 Subject: [PATCH 489/514] Update TicketsController.php --- app/Http/Controllers/TicketsController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/TicketsController.php b/app/Http/Controllers/TicketsController.php index eee949081..447e0cc8d 100644 --- a/app/Http/Controllers/TicketsController.php +++ b/app/Http/Controllers/TicketsController.php @@ -20,7 +20,7 @@ use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Str; -use App\Settings\GeneralSettings; + class TicketsController extends Controller { @@ -42,7 +42,7 @@ public function store(Request $request, GeneralSettings $generalSettings) if (RateLimiter::tooManyAttempts('ticket-send:'.Auth::user()->id, $perMinute = 1)) { return redirect()->back()->with('error', __('Please wait before creating a new Ticket')); } - + $validateData = [ 'title' => 'required|string|max:255', 'ticketcategory' => 'required|numeric', @@ -55,7 +55,7 @@ public function store(Request $request, GeneralSettings $generalSettings) } $this->validate($request, $validateData); - + $ticket = new Ticket( [ 'title' => $request->input('title'), From e762b36690940b45599d9a4cee7f1666210517d6 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 10:27:46 +0100 Subject: [PATCH 490/514] change version to 1.0 --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index bc976a3b5..bdece1310 100644 --- a/config/app.php +++ b/config/app.php @@ -4,7 +4,7 @@ return [ - 'version' => '0.10', + 'version' => '1.0', /* |-------------------------------------------------------------------------- From c64954ef0acfcacc1f5a0917768f7105eba29618 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 11:42:43 +0100 Subject: [PATCH 491/514] Update DiscordServiceProvider.php --- app/Providers/DiscordServiceProvider.php | 28 ++++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/app/Providers/DiscordServiceProvider.php b/app/Providers/DiscordServiceProvider.php index 30d0dd0b7..d8f778800 100644 --- a/app/Providers/DiscordServiceProvider.php +++ b/app/Providers/DiscordServiceProvider.php @@ -15,16 +15,26 @@ public function register() public function boot() { - // Retrieve Discord settings from the Spatie settings class - $discordSettings = app(DiscordSettings::class); + if (config('app.key') == null) return; - // Inject the settings into the config - Config::set('services.discord.client_id', $discordSettings->client_id); - Config::set('services.discord.client_secret', $discordSettings->client_secret); - Config::set('services.discord.redirect', env('APP_URL', 'http://localhost') . '/auth/callback'); + try { + if (DB::table('settings')->where('name','client_id')->where('group','discord')->exists()) { + // Retrieve Discord settings from the Spatie settings class + $discordSettings = app(DiscordSettings::class); - // optional - Config::set('services.discord.allow_gif_avatars', (bool)env('DISCORD_AVATAR_GIF', true)); - Config::set('services.discord.avatar_default_extension', env('DISCORD_EXTENSION_DEFAULT', 'jpg')); + // Inject the settings into the config + Config::set('services.discord.client_id', $discordSettings->client_id); + Config::set('services.discord.client_secret', $discordSettings->client_secret); + Config::set('services.discord.redirect', env('APP_URL', 'http://localhost') . '/auth/callback'); + + // optional + Config::set('services.discord.allow_gif_avatars', (bool)env('DISCORD_AVATAR_GIF', true)); + Config::set('services.discord.avatar_default_extension', env('DISCORD_EXTENSION_DEFAULT', 'jpg')); + } else{ + Log::error("Setting for Discord not found"); + } + } catch (Exception $e) { + Log::error("Couldnt find settings. Probably the installation is not completet. " . $e); + } } } From 0c78f1768e905c357ab368725636e10186d19467 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 11:47:51 +0100 Subject: [PATCH 492/514] Update DiscordServiceProvider.php --- app/Providers/DiscordServiceProvider.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Providers/DiscordServiceProvider.php b/app/Providers/DiscordServiceProvider.php index d8f778800..48cfa49ca 100644 --- a/app/Providers/DiscordServiceProvider.php +++ b/app/Providers/DiscordServiceProvider.php @@ -3,6 +3,8 @@ namespace App\Providers; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; use App\Settings\DiscordSettings; From b10dc36714fffe8d8c4b8cca61197ebd7bdbd7b6 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 11:58:00 +0100 Subject: [PATCH 493/514] Update DiscordServiceProvider.php --- app/Providers/DiscordServiceProvider.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/Providers/DiscordServiceProvider.php b/app/Providers/DiscordServiceProvider.php index 48cfa49ca..5980b46b1 100644 --- a/app/Providers/DiscordServiceProvider.php +++ b/app/Providers/DiscordServiceProvider.php @@ -3,7 +3,6 @@ namespace App\Providers; use Illuminate\Support\Facades\Config; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\ServiceProvider; use App\Settings\DiscordSettings; @@ -20,9 +19,8 @@ public function boot() if (config('app.key') == null) return; try { - if (DB::table('settings')->where('name','client_id')->where('group','discord')->exists()) { + $discordSettings = app(DiscordSettings::class); // Retrieve Discord settings from the Spatie settings class - $discordSettings = app(DiscordSettings::class); // Inject the settings into the config Config::set('services.discord.client_id', $discordSettings->client_id); @@ -32,9 +30,7 @@ public function boot() // optional Config::set('services.discord.allow_gif_avatars', (bool)env('DISCORD_AVATAR_GIF', true)); Config::set('services.discord.avatar_default_extension', env('DISCORD_EXTENSION_DEFAULT', 'jpg')); - } else{ - Log::error("Setting for Discord not found"); - } + } catch (Exception $e) { Log::error("Couldnt find settings. Probably the installation is not completet. " . $e); } From 34407aff9913bcd1b0b154229f77179ff3cf7f8b Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 14:20:50 +0100 Subject: [PATCH 494/514] Update DiscordServiceProvider.php --- app/Providers/DiscordServiceProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Providers/DiscordServiceProvider.php b/app/Providers/DiscordServiceProvider.php index 5980b46b1..85d5965cb 100644 --- a/app/Providers/DiscordServiceProvider.php +++ b/app/Providers/DiscordServiceProvider.php @@ -19,7 +19,7 @@ public function boot() if (config('app.key') == null) return; try { - $discordSettings = app(DiscordSettings::class); + $discordSettings = $this->app->make(DiscordSettings::class); // Retrieve Discord settings from the Spatie settings class // Inject the settings into the config From 5d03a7444b7a9cc5ef7675db63fb8dc5078dd7e7 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 14:22:51 +0100 Subject: [PATCH 495/514] Update DiscordServiceProvider.php --- app/Providers/DiscordServiceProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Providers/DiscordServiceProvider.php b/app/Providers/DiscordServiceProvider.php index 85d5965cb..019fa33b8 100644 --- a/app/Providers/DiscordServiceProvider.php +++ b/app/Providers/DiscordServiceProvider.php @@ -23,8 +23,8 @@ public function boot() // Retrieve Discord settings from the Spatie settings class // Inject the settings into the config - Config::set('services.discord.client_id', $discordSettings->client_id); - Config::set('services.discord.client_secret', $discordSettings->client_secret); + Config::set('services.discord.client_id', $discordSettings->client_id ?: ""); + Config::set('services.discord.client_secret', $discordSettings->client_secret ?: ""); Config::set('services.discord.redirect', env('APP_URL', 'http://localhost') . '/auth/callback'); // optional From b980a091ddcf14dbf7e10ea57d69522b47869b3c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 14:48:22 +0100 Subject: [PATCH 496/514] Fix discord settings omg --- app/Providers/DiscordServiceProvider.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Providers/DiscordServiceProvider.php b/app/Providers/DiscordServiceProvider.php index 019fa33b8..79baf51ca 100644 --- a/app/Providers/DiscordServiceProvider.php +++ b/app/Providers/DiscordServiceProvider.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use App\Settings\DiscordSettings; @@ -17,6 +18,7 @@ public function register() public function boot() { if (config('app.key') == null) return; + if(!Schema::hasTable('old_settings')){ return;} try { $discordSettings = $this->app->make(DiscordSettings::class); From 0bc842c1179aea2e8b5e41555abf53b8c54a10e5 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 11 Nov 2024 15:06:35 +0100 Subject: [PATCH 497/514] Change checking Method --- app/Providers/DiscordServiceProvider.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Providers/DiscordServiceProvider.php b/app/Providers/DiscordServiceProvider.php index 79baf51ca..4d0e7b514 100644 --- a/app/Providers/DiscordServiceProvider.php +++ b/app/Providers/DiscordServiceProvider.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Support\ServiceProvider; use App\Settings\DiscordSettings; +use Exception; class DiscordServiceProvider extends ServiceProvider { @@ -18,7 +19,7 @@ public function register() public function boot() { if (config('app.key') == null) return; - if(!Schema::hasTable('old_settings')){ return;} + if (!Schema::hasColumn('settings', 'payload')) return; try { $discordSettings = $this->app->make(DiscordSettings::class); From f061d0fba4c9b2980614e4adde927be099fb678c Mon Sep 17 00:00:00 2001 From: S0ly <86328249+S0ly@users.noreply.github.com> Date: Tue, 12 Nov 2024 19:52:35 +0100 Subject: [PATCH 498/514] fix: correctly display version --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index bdece1310..7c7b09a3d 100644 --- a/config/app.php +++ b/config/app.php @@ -4,7 +4,7 @@ return [ - 'version' => '1.0', + 'version' => '1.0.0', /* |-------------------------------------------------------------------------- From 7e61f16cf69505d6ebab88b867075888c6631125 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 13 Nov 2024 09:27:26 +0100 Subject: [PATCH 499/514] fix some payment related stuff --- app/Extensions/PaymentGateways/PayPal/PayPalExtension.php | 3 ++- app/Http/Controllers/Admin/PaymentController.php | 2 +- app/Models/ShopProduct.php | 2 +- app/Traits/Coupon.php | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index 981d86ef2..c86973e54 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -57,7 +57,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct 'breakdown' => [ 'item_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), - 'value' => $totalPriceString + 'value' => $shopProduct->price, ], 'tax_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), @@ -75,6 +75,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct ] ]; + try { // Call API with your client and get a response for your call $response = self::getPayPalClient()->execute($request); diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index 82efc3b5f..adafe1d8c 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -141,7 +141,7 @@ public function pay(Request $request) $paymentGateway = $request->payment_method; $couponCode = $request->coupon_code; - $subtotal = $shopProduct->price; + $subtotal = $shopProduct->getTotalPrice(); // Apply Coupon if ($couponCode) { diff --git a/app/Models/ShopProduct.php b/app/Models/ShopProduct.php index 4515f5d79..278183dc8 100644 --- a/app/Models/ShopProduct.php +++ b/app/Models/ShopProduct.php @@ -75,7 +75,7 @@ public function formatToCurrency($value, $locale = 'en_US') public function getTaxPercent() { $generalSettings = new GeneralSettings(); - $tax = $generalSettings->sales_tax; + $tax = intval($generalSettings->sales_tax); return $tax < 0 ? 0 : $tax; } diff --git a/app/Traits/Coupon.php b/app/Traits/Coupon.php index f1d06d27d..7c01efae7 100644 --- a/app/Traits/Coupon.php +++ b/app/Traits/Coupon.php @@ -73,7 +73,7 @@ public function isCouponValid(string $couponCode, User $user, string $productId) { if (is_null($couponCode)) return false; - $coupon = CouponModel::where('code', $couponCode)->first(); + $coupon = CouponModel::where('code', $couponCode)->firstOrFail(); $shopProduct = ShopProduct::findOrFail($productId); if ($coupon->getStatus() == 'USES_LIMIT_REACHED') { From ba585b70ead1e13b631c1e6b6101d245ccb2915d Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 13 Nov 2024 13:05:24 +0100 Subject: [PATCH 500/514] Fix Paymentgateways with coupon and tax --- .../PaymentGateways/PayPal/PayPalExtension.php | 13 +++++++------ .../PaymentGateways/Stripe/StripeExtension.php | 8 +++----- app/Http/Controllers/Admin/PaymentController.php | 13 +++++++------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index c86973e54..04eb97ed9 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -2,17 +2,13 @@ namespace App\Extensions\PaymentGateways\PayPal; -use App\Events\CouponUsedEvent; use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; -use App\Extensions\PaymentGateways\PayPal\PayPalSettings; use App\Classes\PaymentExtension; use App\Enums\PaymentStatus; -use App\Models\PartnerDiscount; use App\Models\Payment; use App\Models\ShopProduct; use App\Models\User; -use App\Models\Coupon; use App\Traits\Coupon as CouponTrait; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -57,12 +53,16 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct 'breakdown' => [ 'item_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), - 'value' => $shopProduct->price, + 'value' => $totalPriceString, ], + + /* Removed due to errors in the coupon discount calculation. Its not used in other paymentgateways aswell and basically nice to have but unnessecary + 'tax_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), - 'value' => $shopProduct->getTaxValue(), + 'value' => round($shopProduct->getTaxValue(), 2), ] + */ ] ] ] @@ -76,6 +76,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct ]; + try { // Call API with your client and get a response for your call $response = self::getPayPalClient()->execute($request); diff --git a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php index b96134317..d2003fe20 100644 --- a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php +++ b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php @@ -5,14 +5,10 @@ use App\Classes\AbstractExtension; use App\Enums\PaymentStatus; use App\Events\PaymentEvent; -use App\Events\CouponUsedEvent; use App\Events\UserUpdateCreditsEvent; -use App\Extensions\PaymentGateways\Stripe\StripeSettings; -use App\Models\PartnerDiscount; use App\Models\Payment; use App\Models\ShopProduct; use App\Models\User; -use App\Models\Coupon; use App\Traits\Coupon as CouponTrait; use App\Notifications\ConfirmPaymentNotification; use Exception; @@ -59,6 +55,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct ], 'quantity' => 1, ], + /* Removed due to errors in the coupon discount calculation. Its not used in other paymentgateways aswell and basically nice to have but unnessecary [ 'price_data' => [ 'currency' => $shopProduct->currency_code, @@ -66,10 +63,11 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct 'name' => __('Tax'), 'description' => $shopProduct->getTaxPercent() . '%', ], - 'unit_amount_decimal' => round($shopProduct->getTaxValue(), 2) * 100, + 'unit_amount_decimal' => round($shopProduct->getTaxValue(), 2), ], 'quantity' => 1, ], + */ ], 'mode' => 'payment', diff --git a/app/Http/Controllers/Admin/PaymentController.php b/app/Http/Controllers/Admin/PaymentController.php index adafe1d8c..3d2da91e7 100644 --- a/app/Http/Controllers/Admin/PaymentController.php +++ b/app/Http/Controllers/Admin/PaymentController.php @@ -147,6 +147,8 @@ public function pay(Request $request) if ($couponCode) { if ($this->isCouponValid($couponCode, $user, $shopProduct->id)) { $subtotal = $this->applyCoupon($couponCode, $subtotal); + event(new CouponUsedEvent($couponCode)); + } } @@ -155,13 +157,15 @@ public function pay(Request $request) if ($subtotal <= 0) { if ($couponCode) { event(new CouponUsedEvent($couponCode)); + } return $this->handleFreeProduct($shopProduct); } - // Format the total price to a readable string $totalPriceString = number_format($subtotal, 2, '.', ''); + //reset the price after coupon use + $shopProduct->price = $totalPriceString; // create a new payment $payment = Payment::create([ @@ -171,10 +175,10 @@ public function pay(Request $request) 'type' => $shopProduct->type, 'status' => 'open', 'amount' => $shopProduct->quantity, - 'price' => $totalPriceString, + 'price' => $shopProduct->price, 'tax_value' => $shopProduct->getTaxValue(), 'tax_percent' => $shopProduct->getTaxPercent(), - 'total_price' => $shopProduct->getTotalPrice(), + 'total_price' => $totalPriceString, 'currency_code' => $shopProduct->currency_code, 'shop_item_product_id' => $shopProduct->id, ]); @@ -182,9 +186,6 @@ public function pay(Request $request) $paymentGatewayExtension = ExtensionHelper::getExtensionClass($paymentGateway); $redirectUrl = $paymentGatewayExtension::getRedirectUrl($payment, $shopProduct, $totalPriceString); - if ($couponCode) { - event(new CouponUsedEvent($couponCode)); - } } catch (Exception $e) { Log::error($e->getMessage()); return redirect()->route('store.index')->with('error', __('Oops, something went wrong! Please try again later.')); From 846b1a9cfecb5ad6e7e95134efcf074ec95a1aec Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Wed, 13 Nov 2024 13:08:54 +0100 Subject: [PATCH 501/514] cleanup double route --- routes/web.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/routes/web.php b/routes/web.php index a8dd8242e..308549d46 100644 --- a/routes/web.php +++ b/routes/web.php @@ -128,8 +128,6 @@ Route::get('roles/datatable', [RoleController::class, 'datatable'])->name('roles.datatable'); Route::resource('roles', RoleController::class); //overview - Route::get('legal', [OverViewController::class, 'index'])->name('overview.index'); - Route::get('overview', [OverViewController::class, 'index'])->name('overview.index'); Route::get('overview/sync', [OverViewController::class, 'syncPterodactyl'])->name('overview.sync'); From 9a747b4c580ede96f77394fc24f9f119a66ba747 Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Wed, 13 Nov 2024 16:17:56 +0200 Subject: [PATCH 502/514] feat(installer): :globe_with_meridians: add UTC timezone option as default in installer --- public/installer/views/timezone-configuration.php | 1 + 1 file changed, 1 insertion(+) diff --git a/public/installer/views/timezone-configuration.php b/public/installer/views/timezone-configuration.php index 3766e73ff..9aaaf595d 100644 --- a/public/installer/views/timezone-configuration.php +++ b/public/installer/views/timezone-configuration.php @@ -16,6 +16,7 @@ <div class="flex flex-col mb-3"> <label for="timezone">Timezone</label> <select id="timezone" name="timezone" required class="px-2 py-2 bg-[#1D2125] border-2 focus:border-sky-500 box-border rounded-md border-transparent outline-none"> + <option value="UTC" selected>UTC</option> <?php foreach (DateTimeZone::listIdentifiers() as $timezoneIdentifier) { if ($timezoneIdentifier === 'UTC') { From 5aff7bf0b5c75f39cf177eeb9f61bec71414e5d5 Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Wed, 13 Nov 2024 16:20:37 +0200 Subject: [PATCH 503/514] chore(installer): :wrench: increase minimum supported PHP version to 8.2 --- public/installer/src/functions/environment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/installer/src/functions/environment.php b/public/installer/src/functions/environment.php index c98223212..456680673 100644 --- a/public/installer/src/functions/environment.php +++ b/public/installer/src/functions/environment.php @@ -52,7 +52,7 @@ function checkExtensions(): array } $requirements = [ - 'minPhp' => '8.1', + 'minPhp' => '8.2', 'maxPhp' => '8.4', // This version is not supported 'mysql' => '5.7.22', ]; From a3bd771f8d01db4c899827abdd2d4a1eb6710d19 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 14 Nov 2024 13:01:29 +0100 Subject: [PATCH 504/514] Fix getAllExtensionClasses() --- app/Helpers/ExtensionHelper.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/Helpers/ExtensionHelper.php b/app/Helpers/ExtensionHelper.php index f465078b0..fd2e653cc 100644 --- a/app/Helpers/ExtensionHelper.php +++ b/app/Helpers/ExtensionHelper.php @@ -62,13 +62,19 @@ public static function getAllExtensionClasses() { $extensions = self::getAllExtensions(); - // replace all slashes with backslashes - $extensions = array_map(fn ($item) => str_replace('/', '\\', $item), $extensions); - // add the ExtensionClass to the end of the namespace - $extensions = array_map(fn ($item) => $item . '\\' . basename($item) . 'Extension', $extensions); - // filter out non existing extension classes + // Replace slashes with backslashes and add the gateway name with "Extension" at the end + $extensions = array_map(function ($item) { + // Convert to backslashes + $item = str_replace('/', '\\', $item); + // Get the last part of the path as the gateway name + $gatewayName = explode('\\', $item); + + // Construct the full class namespace + return $item . '\\' . end($gatewayName) . 'Extension'; + }, $extensions); + + // Filter out non-existing extension classes $extensions = array_filter($extensions, fn ($item) => class_exists($item)); - return $extensions; } From 98921c14fa6602fbba5de57c6b1944252c996660 Mon Sep 17 00:00:00 2001 From: MrWeez <arsenyplis2018@gmail.com> Date: Thu, 14 Nov 2024 15:17:30 +0200 Subject: [PATCH 505/514] =?UTF-8?q?fix(installer):=20=F0=9F=90=9B=20remove?= =?UTF-8?q?=20=3Fstep=20parameter=20from=20URL=20to=20prevent=20unintended?= =?UTF-8?q?=20navigation=20on=20page=20reload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: S0ly <86328249+S0ly@users.noreply.github.com> --- public/installer/index.php | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/public/installer/index.php b/public/installer/index.php index 403ca3cc5..600a3297e 100644 --- a/public/installer/index.php +++ b/public/installer/index.php @@ -62,16 +62,28 @@ if (isset($_GET['step'])) { $stepValue = $_GET['step']; + $currentStep = $_SESSION['current_installation_step']; - if (is_numeric($stepValue) && $stepValue >= 1 && $stepValue <= $_SESSION['last_installation_step']) { - // Step is valid numeric within range: - $_SESSION['current_installation_step'] = $stepValue; - } elseif (strtolower($stepValue) === 'next' && $_SESSION['current_installation_step'] < $_SESSION['last_installation_step']) { - // Move to next step: + if (strtolower($stepValue) === 'next' && $currentStep < $_SESSION['last_installation_step']) { $_SESSION['current_installation_step']++; - } elseif (strtolower($stepValue) === 'previous' && $_SESSION['current_installation_step'] > 1) { - // Move to previous step: - $_SESSION['current_installation_step']--; + // Redirect to clean URL after processing + header('Location: index.php'); + exit; + } + elseif (strtolower($stepValue) === 'previous' && $currentStep > 1) { + if ($stepConfig[$currentStep - 1]['is_revertable']) { + $_SESSION['current_installation_step']--; + header('Location: index.php'); + exit; + } + } + elseif (is_numeric($stepValue)) { + // Only allow accessing previous or current steps + if ($stepValue <= $currentStep && $stepValue >= 1 && $stepValue <= $_SESSION['last_installation_step']) { + $_SESSION['current_installation_step'] = $stepValue; + } + header('Location: index.php'); + exit; } } @@ -85,7 +97,7 @@ include "./views/{$viewName}.php"; include './views/layout-bottom.php'; -// setting / resetting the error message +// setting / reseting the error message $_SESSION['error-message'] = null; ?> \ No newline at end of file From f36b1d34194a039fafd3606933450677c6cecb5c Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 14 Nov 2024 22:26:41 +0100 Subject: [PATCH 506/514] fix mercado payment gateway --- .../PaymentGateways/MercadoPago/MercadoPagoExtension.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php index 82a067722..be280ba51 100644 --- a/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php +++ b/app/Extensions/PaymentGateways/MercadoPago/MercadoPagoExtension.php @@ -125,11 +125,11 @@ static function Webhook(Request $request): JsonResponse if ($notification == '123456') return response()->json(['success' => true], 200); /** - * Check action have payment.*, + * Check action have payment.*, * what is expected for this type of api */ if (str_contains($action, 'payment')) { - $url = "https://api.mercadopago.com/v1/payments/" . $notificationId; + $url = "https://api.mercadopago.com/v1/payments/" . $notification; $settings = new MercadoPagoSettings(); $response = Http::withHeaders([ 'Content-Type' => 'application/json', From 04130ed76552794f12bc6e2cf0eb8d645990a55a Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Mon, 18 Nov 2024 09:09:26 +0100 Subject: [PATCH 507/514] fix stripe --- .../PaymentGateways/Stripe/StripeExtension.php | 9 ++++++++- app/Helpers/ExtensionHelper.php | 5 +++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php index d2003fe20..12f80844f 100644 --- a/app/Extensions/PaymentGateways/Stripe/StripeExtension.php +++ b/app/Extensions/PaymentGateways/Stripe/StripeExtension.php @@ -51,7 +51,7 @@ public static function getRedirectUrl(Payment $payment, ShopProduct $shopProduct 'name' => $shopProduct->display, 'description' => $shopProduct->description, ], - 'unit_amount_decimal' => $totalPriceString, + 'unit_amount_decimal' => self::priceInCents($totalPriceString), ], 'quantity' => 1, ], @@ -363,4 +363,11 @@ public static function checkPriceAmount(float $amount, string $currencyCode, st ]; return $amount >= $minimums[$currencyCode][$payment_method]; } + public static function priceInCents($price){ + $centPrice = str_replace(".", "", $price); + $centPrice = str_replace(",", "", $centPrice); + //$centPrice = str_pad($centPrice, 4, '0', STR_PAD_RIGHT); + return $centPrice; + } + } diff --git a/app/Helpers/ExtensionHelper.php b/app/Helpers/ExtensionHelper.php index fd2e653cc..6bf0d3726 100644 --- a/app/Helpers/ExtensionHelper.php +++ b/app/Helpers/ExtensionHelper.php @@ -68,9 +68,10 @@ public static function getAllExtensionClasses() $item = str_replace('/', '\\', $item); // Get the last part of the path as the gateway name $gatewayName = explode('\\', $item); - + $gatewayName = array_pop($gatewayName); + // Construct the full class namespace - return $item . '\\' . end($gatewayName) . 'Extension'; + return $item . '\\' . $gatewayName . 'Extension'; }, $extensions); // Filter out non-existing extension classes From 29b706245a2024eb3df5a1766fb16031f02462eb Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Mon, 18 Nov 2024 12:48:22 +0100 Subject: [PATCH 508/514] fix: Do not unnullify columns --- ..._add_required_nullable_and_text_columns.php | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/database/migrations/2024_07_24_175733_add_required_nullable_and_text_columns.php b/database/migrations/2024_07_24_175733_add_required_nullable_and_text_columns.php index 41f2c6c0f..a28f51083 100644 --- a/database/migrations/2024_07_24_175733_add_required_nullable_and_text_columns.php +++ b/database/migrations/2024_07_24_175733_add_required_nullable_and_text_columns.php @@ -28,19 +28,5 @@ public function up(): void /** * Reverse the migrations. */ - public function down(): void - { - Schema::table('locations', function (Blueprint $table) { - $table->string('description')->change(); - }); - Schema::table('nodes', function (Blueprint $table) { - $table->string('description')->change(); - }); - Schema::table('nests', function (Blueprint $table) { - $table->string('description')->change(); - }); - Schema::table('eggs', function (Blueprint $table) { - $table->string('description')->change(); - }); - } -}; \ No newline at end of file + public function down(): void {} +}; From 3c49a01607d7a2e99e9c0d6468f117511c4d6023 Mon Sep 17 00:00:00 2001 From: IceToast <social@icetoast.de> Date: Mon, 18 Nov 2024 12:48:33 +0100 Subject: [PATCH 509/514] chore: Update deps --- composer.lock | 1174 +++++++++++++++++++++++-------------------------- 1 file changed, 557 insertions(+), 617 deletions(-) diff --git a/composer.lock b/composer.lock index 20e494507..c8907a7ff 100644 --- a/composer.lock +++ b/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "aws/aws-crt-php", - "version": "v1.2.6", + "version": "v1.2.7", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "a63485b65b6b3367039306496d49737cf1995408" + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408", - "reference": "a63485b65b6b3367039306496d49737cf1995408", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", "shasum": "" }, "require": { @@ -56,22 +56,22 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" }, - "time": "2024-06-13T17:21:28+00:00" + "time": "2024-10-18T22:15:13+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.316.3", + "version": "3.328.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "e832e594b3c213760e067e15ef2739f77505e832" + "reference": "a99b58e166ae367f2b067937afb04e843e900745" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e832e594b3c213760e067e15ef2739f77505e832", - "reference": "e832e594b3c213760e067e15ef2739f77505e832", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a99b58e166ae367f2b067937afb04e843e900745", + "reference": "a99b58e166ae367f2b067937afb04e843e900745", "shasum": "" }, "require": { @@ -124,7 +124,10 @@ ], "psr-4": { "Aws\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/data/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -151,9 +154,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.316.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.328.0" }, - "time": "2024-07-12T18:07:23+00:00" + "time": "2024-11-15T19:06:57+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -510,16 +513,16 @@ }, { "name": "doctrine/dbal", - "version": "4.0.4", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "50fda19f80724b55ff770bb4ff352407008e63c5" + "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/50fda19f80724b55ff770bb4ff352407008e63c5", - "reference": "50fda19f80724b55ff770bb4ff352407008e63c5", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/dadd35300837a3a2184bd47d403333b15d0a9bd0", + "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0", "shasum": "" }, "require": { @@ -532,16 +535,16 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "1.11.5", + "phpstan/phpstan": "1.12.6", "phpstan/phpstan-phpunit": "1.4.0", "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "10.5.22", + "phpunit/phpunit": "10.5.30", "psalm/plugin-phpunit": "0.19.0", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.1", + "squizlabs/php_codesniffer": "3.10.2", "symfony/cache": "^6.3.8|^7.0", "symfony/console": "^5.4|^6.3|^7.0", - "vimeo/psalm": "5.24.0" + "vimeo/psalm": "5.25.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -598,7 +601,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.0.4" + "source": "https://github.com/doctrine/dbal/tree/4.2.1" }, "funding": [ { @@ -614,7 +617,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T11:57:23+00:00" + "time": "2024-10-10T18:01:27+00:00" }, { "name": "doctrine/deprecations", @@ -895,16 +898,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.3", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" + "reference": "8c784d071debd117328803d86b2097615b457500" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", + "reference": "8c784d071debd117328803d86b2097615b457500", "shasum": "" }, "require": { @@ -917,10 +920,14 @@ "require-dev": { "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.0", - "phpstan/phpstan-webmozart-assert": "^1.0", "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -944,7 +951,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" }, "funding": [ { @@ -952,7 +959,7 @@ "type": "github" } ], - "time": "2023-08-10T19:36:49+00:00" + "time": "2024-10-09T13:47:03+00:00" }, { "name": "egulias/email-validator", @@ -1398,16 +1405,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -1461,7 +1468,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.3" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -1477,7 +1484,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T10:29:17+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", @@ -1805,16 +1812,16 @@ }, { "name": "laravel/framework", - "version": "v11.18.1", + "version": "v11.32.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b19ba518c56852567e99fbae9321bc436c2cc5a8" + "reference": "bc2aad63f83ee5089be7b21cf29d645ccf31e927" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b19ba518c56852567e99fbae9321bc436c2cc5a8", - "reference": "b19ba518c56852567e99fbae9321bc436c2cc5a8", + "url": "https://api.github.com/repos/laravel/framework/zipball/bc2aad63f83ee5089be7b21cf29d645ccf31e927", + "reference": "bc2aad63f83ee5089be7b21cf29d645ccf31e927", "shasum": "" }, "require": { @@ -1833,7 +1840,7 @@ "fruitcake/php-cors": "^1.3", "guzzlehttp/guzzle": "^7.8", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.18", + "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", @@ -1876,6 +1883,7 @@ "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", "illuminate/conditionable": "self.version", "illuminate/config": "self.version", "illuminate/console": "self.version", @@ -1918,7 +1926,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.6", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.1.5", + "orchestra/testbench-core": "^9.5", "pda/pheanstalk": "^5.0", "phpstan/phpstan": "^1.11.5", "phpunit/phpunit": "^10.5|^11.0", @@ -1976,6 +1984,8 @@ "src/Illuminate/Events/functions.php", "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Support/functions.php", "src/Illuminate/Support/helpers.php" ], "psr-4": { @@ -2007,25 +2017,25 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-07-26T10:39:29+00:00" + "time": "2024-11-15T17:04:33+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.24", + "version": "v0.3.2", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "409b0b4305273472f3754826e68f4edbd0150149" + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/409b0b4305273472f3754826e68f4edbd0150149", - "reference": "409b0b4305273472f3754826e68f4edbd0150149", + "url": "https://api.github.com/repos/laravel/prompts/zipball/0e0535747c6b8d6d10adca8b68293cf4517abb0f", + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f", "shasum": "" }, "require": { + "composer-runtime-api": "^2.2", "ext-mbstring": "*", - "illuminate/collections": "^10.0|^11.0", "php": "^8.1", "symfony/console": "^6.2|^7.0" }, @@ -2034,8 +2044,9 @@ "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { + "illuminate/collections": "^10.0|^11.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3", + "pestphp/pest": "^2.3|^3.4", "phpstan/phpstan": "^1.11", "phpstan/phpstan-mockery": "^1.1" }, @@ -2045,7 +2056,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.1.x-dev" + "dev-main": "0.3.x-dev" } }, "autoload": { @@ -2063,32 +2074,33 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.24" + "source": "https://github.com/laravel/prompts/tree/v0.3.2" }, - "time": "2024-06-17T13:58:22+00:00" + "time": "2024-11-12T14:59:47+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.3", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754" + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96", + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96", "shasum": "" }, "require": { "php": "^7.3|^8.0" }, "require-dev": { - "nesbot/carbon": "^2.61", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", "pestphp/pest": "^1.21.3", "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11" + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" }, "type": "library", "extra": { @@ -2125,20 +2137,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-11-08T14:08:06+00:00" + "time": "2024-11-11T17:06:04+00:00" }, { "name": "laravel/socialite", - "version": "v5.15.1", + "version": "v5.16.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029" + "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/cc02625f0bd1f95dc3688eb041cce0f1e709d029", - "reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029", + "url": "https://api.github.com/repos/laravel/socialite/zipball/40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", + "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", "shasum": "" }, "require": { @@ -2197,20 +2209,20 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2024-06-28T20:09:34+00:00" + "time": "2024-09-03T09:46:57+00:00" }, { "name": "laravel/tinker", - "version": "v2.9.0", + "version": "v2.10.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe" + "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe", - "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "url": "https://api.github.com/repos/laravel/tinker/zipball/ba4d51eb56de7711b3a37d63aa0643e99a339ae5", + "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5", "shasum": "" }, "require": { @@ -2261,9 +2273,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.9.0" + "source": "https://github.com/laravel/tinker/tree/v2.10.0" }, - "time": "2024-01-04T16:10:04+00:00" + "time": "2024-09-23T13:32:56+00:00" }, { "name": "laravel/ui", @@ -2396,16 +2408,16 @@ }, { "name": "league/commonmark", - "version": "2.5.1", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c" + "reference": "b650144166dfa7703e62a22e493b853b58d874b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ac815920de0eff6de947eac0a6a94e5ed0fb147c", - "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", + "reference": "b650144166dfa7703e62a22e493b853b58d874b0", "shasum": "" }, "require": { @@ -2418,8 +2430,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.31.0", - "commonmark/commonmark.js": "0.31.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -2498,7 +2510,7 @@ "type": "tidelift" } ], - "time": "2024-07-24T12:52:09+00:00" + "time": "2024-08-16T11:46:16+00:00" }, { "name": "league/config", @@ -2584,16 +2596,16 @@ }, { "name": "league/flysystem", - "version": "3.28.0", + "version": "3.29.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c" + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", "shasum": "" }, "require": { @@ -2661,22 +2673,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" }, - "time": "2024-05-22T10:09:12+00:00" + "time": "2024-10-08T08:58:34+00:00" }, { "name": "league/flysystem-aws-s3-v3", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "22071ef1604bc776f5ff2468ac27a752514665c8" + "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/22071ef1604bc776f5ff2468ac27a752514665c8", - "reference": "22071ef1604bc776f5ff2468ac27a752514665c8", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/c6ff6d4606e48249b63f269eba7fabdb584e76a9", + "reference": "c6ff6d4606e48249b63f269eba7fabdb584e76a9", "shasum": "" }, "require": { @@ -2716,22 +2728,22 @@ "storage" ], "support": { - "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.29.0" }, - "time": "2024-05-06T20:05:52+00:00" + "time": "2024-08-17T13:10:48+00:00" }, { "name": "league/flysystem-local", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40" + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", "shasum": "" }, "require": { @@ -2765,22 +2777,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" }, - "time": "2024-05-06T20:05:52+00:00" + "time": "2024-08-09T21:24:39+00:00" }, { "name": "league/mime-type-detection", - "version": "1.15.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", "shasum": "" }, "require": { @@ -2811,7 +2823,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { @@ -2823,7 +2835,7 @@ "type": "tidelift" } ], - "time": "2024-01-28T23:22:08+00:00" + "time": "2024-09-21T08:32:55+00:00" }, { "name": "league/oauth1-client", @@ -2970,16 +2982,16 @@ }, { "name": "monolog/monolog", - "version": "3.7.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" + "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/32e515fdc02cdafbe4593e30a9350d486b125b67", + "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67", "shasum": "" }, "require": { @@ -2999,12 +3011,14 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.5.17", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", - "ruflin/elastica": "^7", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -3055,7 +3069,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.7.0" + "source": "https://github.com/Seldaek/monolog/tree/3.8.0" }, "funding": [ { @@ -3067,20 +3081,20 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:40:51+00:00" + "time": "2024-11-12T13:57:08+00:00" }, { "name": "mtdowling/jmespath.php", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", "shasum": "" }, "require": { @@ -3097,7 +3111,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -3131,26 +3145,26 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" }, - "time": "2023-08-25T10:54:48+00:00" + "time": "2024-09-04T18:46:31+00:00" }, { "name": "nesbot/carbon", - "version": "3.7.0", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139" + "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cb4374784c87d0a0294e8513a52eb63c0aff3139", - "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e1268cdbc486d97ce23fef2c666dc3c6b6de9947", + "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947", "shasum": "" }, "require": { - "carbonphp/carbon-doctrine-types": "*", + "carbonphp/carbon-doctrine-types": "<100.0", "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", @@ -3239,28 +3253,28 @@ "type": "tidelift" } ], - "time": "2024-07-16T22:29:20+00:00" + "time": "2024-11-07T17:46:48+00:00" }, { "name": "nette/schema", - "version": "v1.3.0", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.3" + "php": "8.1 - 8.4" }, "require-dev": { - "nette/tester": "^2.4", + "nette/tester": "^2.5.2", "phpstan/phpstan-nette": "^1.0", "tracy/tracy": "^2.8" }, @@ -3299,26 +3313,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.0" + "source": "https://github.com/nette/schema/tree/v1.3.2" }, - "time": "2023-12-11T11:54:22+00:00" + "time": "2024-10-06T23:10:23+00:00" }, { "name": "nette/utils", - "version": "v4.0.4", + "version": "v4.0.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", - "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", "shasum": "" }, "require": { - "php": ">=8.0 <8.4" + "php": "8.0 - 8.4" }, "conflict": { "nette/finder": "<3", @@ -3385,22 +3399,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.4" + "source": "https://github.com/nette/utils/tree/v4.0.5" }, - "time": "2024-01-17T16:50:36+00:00" + "time": "2024-08-07T15:39:19+00:00" }, { "name": "nikic/php-parser", - "version": "v5.1.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", - "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { @@ -3443,38 +3457,37 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2024-07-01T20:03:41+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.0.1", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a" + "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/58c4c58cf23df7f498daeb97092e34f5259feb6a", - "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/42c84e4e8090766bbd6445d06cd6e57650626ea3", + "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.0.4" + "symfony/console": "^7.1.5" }, "require-dev": { - "ergebnis/phpstan-rules": "^2.2.0", - "illuminate/console": "^11.0.0", - "laravel/pint": "^1.14.0", - "mockery/mockery": "^1.6.7", - "pestphp/pest": "^2.34.1", - "phpstan/phpstan": "^1.10.59", - "phpstan/phpstan-strict-rules": "^1.5.2", - "symfony/var-dumper": "^7.0.4", + "illuminate/console": "^11.28.0", + "laravel/pint": "^1.18.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0", + "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^7.1.5", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -3517,7 +3530,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.0.1" + "source": "https://github.com/nunomaduro/termwind/tree/v2.2.0" }, "funding": [ { @@ -3533,7 +3546,7 @@ "type": "github" } ], - "time": "2024-03-06T16:17:14+00:00" + "time": "2024-10-15T16:15:16+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -3702,7 +3715,7 @@ "support": { "source": "https://github.com/paypal/Checkout-PHP-SDK/tree/1.0.2" }, - "abandoned": true, + "abandoned": "paypal/paypal-server-sdk", "time": "2021-09-21T20:57:38+00:00" }, { @@ -3893,23 +3906,23 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -3945,9 +3958,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpoption/phpoption", @@ -4026,16 +4039,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.39", + "version": "3.0.42", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485" + "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/211ebc399c6e73c225a018435fe5ae209d1d1485", - "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98", + "reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98", "shasum": "" }, "require": { @@ -4116,7 +4129,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.39" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.42" }, "funding": [ { @@ -4132,34 +4145,34 @@ "type": "tidelift" } ], - "time": "2024-06-24T06:27:33+00:00" + "time": "2024-09-16T03:06:04+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -4177,9 +4190,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" }, - "time": "2024-05-31T08:52:43+00:00" + "time": "2024-10-13T11:29:49+00:00" }, { "name": "predis/predis", @@ -4604,16 +4617,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -4648,9 +4661,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", @@ -5079,24 +5092,24 @@ }, { "name": "sabberworm/php-css-parser", - "version": "v8.6.0", + "version": "v8.7.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70" + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d2fb94a9641be84d79c7548c6d39bbebba6e9a70", - "reference": "d2fb94a9641be84d79c7548c6d39bbebba6e9a70", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f414ff953002a9b18e3a116f5e462c56f21237cf", + "reference": "f414ff953002a9b18e3a116f5e462c56f21237cf", "shasum": "" }, "require": { "ext-iconv": "*", - "php": ">=5.6.20" + "php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.27" + "phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.40" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -5138,9 +5151,9 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.6.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.7.0" }, - "time": "2024-07-01T07:33:21+00:00" + "time": "2024-10-27T17:38:32+00:00" }, { "name": "socialiteproviders/discord", @@ -5194,16 +5207,16 @@ }, { "name": "socialiteproviders/manager", - "version": "v4.6.0", + "version": "v4.7.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21" + "reference": "ab0691b82cec77efd90154c78f1854903455c82f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/dea5190981c31b89e52259da9ab1ca4e2b258b21", - "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/ab0691b82cec77efd90154c78f1854903455c82f", + "reference": "ab0691b82cec77efd90154c78f1854903455c82f", "shasum": "" }, "require": { @@ -5264,20 +5277,20 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2024-05-04T07:57:39+00:00" + "time": "2024-11-10T01:56:18+00:00" }, { "name": "spatie/laravel-activitylog", - "version": "4.8.0", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-activitylog.git", - "reference": "eb6f37dd40af950ce10cf5280f0acfa3e08c3bff" + "reference": "e0fc28178515a5396f48e107ed697719189bbe02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/eb6f37dd40af950ce10cf5280f0acfa3e08c3bff", - "reference": "eb6f37dd40af950ce10cf5280f0acfa3e08c3bff", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/e0fc28178515a5396f48e107ed697719189bbe02", + "reference": "e0fc28178515a5396f48e107ed697719189bbe02", "shasum": "" }, "require": { @@ -5343,7 +5356,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-activitylog/issues", - "source": "https://github.com/spatie/laravel-activitylog/tree/4.8.0" + "source": "https://github.com/spatie/laravel-activitylog/tree/4.9.0" }, "funding": [ { @@ -5355,20 +5368,20 @@ "type": "github" } ], - "time": "2024-03-08T22:28:17+00:00" + "time": "2024-10-18T13:38:47+00:00" }, { "name": "spatie/laravel-package-tools", - "version": "1.16.4", + "version": "1.16.5", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53" + "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", - "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/c7413972cf22ffdff97b68499c22baa04eddb6a2", + "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2", "shasum": "" }, "require": { @@ -5407,7 +5420,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.4" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.5" }, "funding": [ { @@ -5415,20 +5428,20 @@ "type": "github" } ], - "time": "2024-03-20T07:29:11+00:00" + "time": "2024-08-27T18:56:10+00:00" }, { "name": "spatie/laravel-permission", - "version": "6.9.0", + "version": "6.10.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-permission.git", - "reference": "fe973a58b44380d0e8620107259b7bda22f70408" + "reference": "8bb69d6d67387f7a00d93a2f5fab98860f06e704" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/fe973a58b44380d0e8620107259b7bda22f70408", - "reference": "fe973a58b44380d0e8620107259b7bda22f70408", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/8bb69d6d67387f7a00d93a2f5fab98860f06e704", + "reference": "8bb69d6d67387f7a00d93a2f5fab98860f06e704", "shasum": "" }, "require": { @@ -5439,6 +5452,7 @@ "php": "^8.0" }, "require-dev": { + "larastan/larastan": "^1.0|^2.0", "laravel/passport": "^11.0|^12.0", "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", "phpunit/phpunit": "^9.4|^10.1" @@ -5489,7 +5503,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-permission/issues", - "source": "https://github.com/spatie/laravel-permission/tree/6.9.0" + "source": "https://github.com/spatie/laravel-permission/tree/6.10.1" }, "funding": [ { @@ -5497,20 +5511,20 @@ "type": "github" } ], - "time": "2024-06-22T23:04:52+00:00" + "time": "2024-11-08T18:45:41+00:00" }, { "name": "spatie/laravel-query-builder", - "version": "6.0.1", + "version": "6.2.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-query-builder.git", - "reference": "69a6fd38c1515e42aec0df10d8adb8465f2f1f79" + "reference": "64f0453f4dea6a6fabf1ce4ddbb553e14da67bb6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/69a6fd38c1515e42aec0df10d8adb8465f2f1f79", - "reference": "69a6fd38c1515e42aec0df10d8adb8465f2f1f79", + "url": "https://api.github.com/repos/spatie/laravel-query-builder/zipball/64f0453f4dea6a6fabf1ce4ddbb553e14da67bb6", + "reference": "64f0453f4dea6a6fabf1ce4ddbb553e14da67bb6", "shasum": "" }, "require": { @@ -5571,20 +5585,20 @@ "type": "custom" } ], - "time": "2024-05-21T12:12:10+00:00" + "time": "2024-10-03T11:10:10+00:00" }, { "name": "spatie/laravel-settings", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-settings.git", - "reference": "395066797823856638a0a2feb243b396a94e22e5" + "reference": "2da8cb5b051678725476b299ef8e13b2e5015260" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-settings/zipball/395066797823856638a0a2feb243b396a94e22e5", - "reference": "395066797823856638a0a2feb243b396a94e22e5", + "url": "https://api.github.com/repos/spatie/laravel-settings/zipball/2da8cb5b051678725476b299ef8e13b2e5015260", + "reference": "2da8cb5b051678725476b299ef8e13b2e5015260", "shasum": "" }, "require": { @@ -5646,7 +5660,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-settings/issues", - "source": "https://github.com/spatie/laravel-settings/tree/3.3.2" + "source": "https://github.com/spatie/laravel-settings/tree/3.4.0" }, "funding": [ { @@ -5658,7 +5672,7 @@ "type": "github" } ], - "time": "2024-03-22T07:50:04+00:00" + "time": "2024-09-20T13:48:17+00:00" }, { "name": "spatie/laravel-validation-rules", @@ -5856,16 +5870,16 @@ }, { "name": "symfony/clock", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7" + "reference": "97bebc53548684c17ed696bc8af016880f0f098d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7", - "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7", + "url": "https://api.github.com/repos/symfony/clock/zipball/97bebc53548684c17ed696bc8af016880f0f098d", + "reference": "97bebc53548684c17ed696bc8af016880f0f098d", "shasum": "" }, "require": { @@ -5910,7 +5924,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.1.1" + "source": "https://github.com/symfony/clock/tree/v7.1.6" }, "funding": [ { @@ -5926,20 +5940,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/console", - "version": "v7.1.3", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9" + "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", - "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9", + "url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5", + "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5", "shasum": "" }, "require": { @@ -6003,7 +6017,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.3" + "source": "https://github.com/symfony/console/tree/v7.1.8" }, "funding": [ { @@ -6019,20 +6033,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-11-06T14:23:19+00:00" }, { "name": "symfony/css-selector", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4" + "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4", - "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", + "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", "shasum": "" }, "require": { @@ -6068,7 +6082,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.1.1" + "source": "https://github.com/symfony/css-selector/tree/v7.1.6" }, "funding": [ { @@ -6084,7 +6098,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6155,16 +6169,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.1.3", + "version": "v7.1.7", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "432bb369952795c61ca1def65e078c4a80dad13c" + "reference": "010e44661f4c6babaf8c4862fe68c24a53903342" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", - "reference": "432bb369952795c61ca1def65e078c4a80dad13c", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/010e44661f4c6babaf8c4862fe68c24a53903342", + "reference": "010e44661f4c6babaf8c4862fe68c24a53903342", "shasum": "" }, "require": { @@ -6210,7 +6224,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.1.3" + "source": "https://github.com/symfony/error-handler/tree/v7.1.7" }, "funding": [ { @@ -6226,20 +6240,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T13:02:51+00:00" + "time": "2024-11-05T15:34:55+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + "reference": "87254c78dd50721cfd015b62277a8281c5589702" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", - "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702", + "reference": "87254c78dd50721cfd015b62277a8281c5589702", "shasum": "" }, "require": { @@ -6290,7 +6304,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6" }, "funding": [ { @@ -6306,7 +6320,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6386,16 +6400,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.3", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "717c6329886f32dc65e27461f80f2a465412fdca" + "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca", - "reference": "717c6329886f32dc65e27461f80f2a465412fdca", + "url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8", + "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8", "shasum": "" }, "require": { @@ -6430,7 +6444,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.3" + "source": "https://github.com/symfony/finder/tree/v7.1.6" }, "funding": [ { @@ -6446,20 +6460,20 @@ "type": "tidelift" } ], - "time": "2024-07-24T07:08:44+00:00" + "time": "2024-10-01T08:31:23+00:00" }, { "name": "symfony/http-client", - "version": "v7.1.3", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "b79858aa7a051ea791b0d50269a234a0b50cb231" + "reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/b79858aa7a051ea791b0d50269a234a0b50cb231", - "reference": "b79858aa7a051ea791b0d50269a234a0b50cb231", + "url": "https://api.github.com/repos/symfony/http-client/zipball/c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a", + "reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a", "shasum": "" }, "require": { @@ -6524,7 +6538,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.1.3" + "source": "https://github.com/symfony/http-client/tree/v7.1.8" }, "funding": [ { @@ -6540,7 +6554,7 @@ "type": "tidelift" } ], - "time": "2024-07-17T06:10:24+00:00" + "time": "2024-11-13T13:40:27+00:00" }, { "name": "symfony/http-client-contracts", @@ -6622,16 +6636,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.1.3", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" + "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", - "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4419ec69ccfc3f725a4de7c20e4e57626d10112", + "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112", "shasum": "" }, "require": { @@ -6641,12 +6655,12 @@ }, "conflict": { "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -6679,7 +6693,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.1.8" }, "funding": [ { @@ -6695,20 +6709,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-11-09T09:16:45+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.3", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186" + "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db9702f3a04cc471ec8c70e881825db26ac5f186", - "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", + "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", "shasum": "" }, "require": { @@ -6793,7 +6807,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/v7.1.3" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.8" }, "funding": [ { @@ -6809,20 +6823,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T14:58:15+00:00" + "time": "2024-11-13T14:25:32+00:00" }, { "name": "symfony/intl", - "version": "v7.1.1", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c" + "reference": "e56b243fc0afa5a12bd11dace4002ada5a7d99f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c", - "reference": "66c1ecda092b1130ada2cf5f59dacfd5b6e9c99c", + "url": "https://api.github.com/repos/symfony/intl/zipball/e56b243fc0afa5a12bd11dace4002ada5a7d99f8", + "reference": "e56b243fc0afa5a12bd11dace4002ada5a7d99f8", "shasum": "" }, "require": { @@ -6879,7 +6893,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v7.1.1" + "source": "https://github.com/symfony/intl/tree/v7.1.8" }, "funding": [ { @@ -6895,20 +6909,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-11-08T15:46:42+00:00" }, { "name": "symfony/mailer", - "version": "v7.1.2", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee" + "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee", - "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee", + "url": "https://api.github.com/repos/symfony/mailer/zipball/69c9948451fb3a6a4d47dc8261d1794734e76cdd", + "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd", "shasum": "" }, "require": { @@ -6959,7 +6973,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.1.2" + "source": "https://github.com/symfony/mailer/tree/v7.1.6" }, "funding": [ { @@ -6975,20 +6989,20 @@ "type": "tidelift" } ], - "time": "2024-06-28T08:00:31+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/mailgun-mailer", - "version": "v7.1.3", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/mailgun-mailer.git", - "reference": "dac02fe68e9306849703025511c56f10701696a8" + "reference": "b0117bf42b6dd8dfcfcab2a7e18508b594520b5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/dac02fe68e9306849703025511c56f10701696a8", - "reference": "dac02fe68e9306849703025511c56f10701696a8", + "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/b0117bf42b6dd8dfcfcab2a7e18508b594520b5a", + "reference": "b0117bf42b6dd8dfcfcab2a7e18508b594520b5a", "shasum": "" }, "require": { @@ -7028,7 +7042,7 @@ "description": "Symfony Mailgun Mailer Bridge", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailgun-mailer/tree/v7.1.3" + "source": "https://github.com/symfony/mailgun-mailer/tree/v7.1.6" }, "funding": [ { @@ -7044,20 +7058,20 @@ "type": "tidelift" } ], - "time": "2024-07-04T11:20:59+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/mime", - "version": "v7.1.2", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc" + "reference": "caa1e521edb2650b8470918dfe51708c237f0598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/26a00b85477e69a4bab63b66c5dce64f18b0cbfc", - "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc", + "url": "https://api.github.com/repos/symfony/mime/zipball/caa1e521edb2650b8470918dfe51708c237f0598", + "reference": "caa1e521edb2650b8470918dfe51708c237f0598", "shasum": "" }, "require": { @@ -7112,7 +7126,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.2" + "source": "https://github.com/symfony/mime/tree/v7.1.6" }, "funding": [ { @@ -7128,24 +7142,24 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-10-25T15:11:02+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -7191,7 +7205,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -7207,24 +7221,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -7269,7 +7283,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -7285,26 +7299,25 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", - "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" @@ -7353,7 +7366,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" }, "funding": [ { @@ -7369,24 +7382,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -7434,7 +7447,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -7450,24 +7463,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -7514,7 +7527,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -7530,97 +7543,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "10112722600777e02d2745716b70c5db4ca70442" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", - "reference": "10112722600777e02d2745716b70c5db4ca70442", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -7667,7 +7607,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -7683,24 +7623,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -7743,7 +7683,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -7759,24 +7699,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:35:24+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-uuid": "*" @@ -7822,7 +7762,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" }, "funding": [ { @@ -7838,20 +7778,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v7.1.3", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" + "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", - "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892", + "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892", "shasum": "" }, "require": { @@ -7883,7 +7823,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.3" + "source": "https://github.com/symfony/process/tree/v7.1.8" }, "funding": [ { @@ -7899,20 +7839,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:44:47+00:00" + "time": "2024-11-06T14:23:19+00:00" }, { "name": "symfony/routing", - "version": "v7.1.3", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0" + "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", - "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0", + "url": "https://api.github.com/repos/symfony/routing/zipball/66a2c469f6c22d08603235c46a20007c0701ea0a", + "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a", "shasum": "" }, "require": { @@ -7964,7 +7904,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.1.3" + "source": "https://github.com/symfony/routing/tree/v7.1.6" }, "funding": [ { @@ -7980,7 +7920,7 @@ "type": "tidelift" } ], - "time": "2024-07-17T06:10:24+00:00" + "time": "2024-10-01T08:31:23+00:00" }, { "name": "symfony/service-contracts", @@ -8067,16 +8007,16 @@ }, { "name": "symfony/string", - "version": "v7.1.3", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07" + "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07", - "reference": "ea272a882be7f20cad58d5d78c215001617b7f07", + "url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281", + "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281", "shasum": "" }, "require": { @@ -8134,7 +8074,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.3" + "source": "https://github.com/symfony/string/tree/v7.1.8" }, "funding": [ { @@ -8150,20 +8090,20 @@ "type": "tidelift" } ], - "time": "2024-07-22T10:25:37+00:00" + "time": "2024-11-13T13:31:21+00:00" }, { "name": "symfony/translation", - "version": "v7.1.3", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1" + "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1", - "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1", + "url": "https://api.github.com/repos/symfony/translation/zipball/b9f72ab14efdb6b772f85041fa12f820dee8d55f", + "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f", "shasum": "" }, "require": { @@ -8228,7 +8168,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.1.3" + "source": "https://github.com/symfony/translation/tree/v7.1.6" }, "funding": [ { @@ -8244,7 +8184,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-09-28T12:35:13+00:00" }, { "name": "symfony/translation-contracts", @@ -8326,16 +8266,16 @@ }, { "name": "symfony/uid", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277" + "reference": "65befb3bb2d503bbffbd08c815aa38b472999917" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/bb59febeecc81528ff672fad5dab7f06db8c8277", - "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277", + "url": "https://api.github.com/repos/symfony/uid/zipball/65befb3bb2d503bbffbd08c815aa38b472999917", + "reference": "65befb3bb2d503bbffbd08c815aa38b472999917", "shasum": "" }, "require": { @@ -8380,7 +8320,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.1.1" + "source": "https://github.com/symfony/uid/tree/v7.1.6" }, "funding": [ { @@ -8396,20 +8336,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.3", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f" + "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/86af4617cca75a6e28598f49ae0690f3b9d4591f", - "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", + "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", "shasum": "" }, "require": { @@ -8463,7 +8403,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.3" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.8" }, "funding": [ { @@ -8479,7 +8419,7 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:41:01+00:00" + "time": "2024-11-08T15:46:42+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8752,16 +8692,16 @@ }, { "name": "yajra/laravel-datatables-oracle", - "version": "v11.1.3", + "version": "v11.1.5", "source": { "type": "git", "url": "https://github.com/yajra/laravel-datatables.git", - "reference": "78ecbc43025a24b92c307664c0cf395fea56c215" + "reference": "158f2e9cf76d500c707a0ebd6cd2079cd87b8d4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/78ecbc43025a24b92c307664c0cf395fea56c215", - "reference": "78ecbc43025a24b92c307664c0cf395fea56c215", + "url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/158f2e9cf76d500c707a0ebd6cd2079cd87b8d4a", + "reference": "158f2e9cf76d500c707a0ebd6cd2079cd87b8d4a", "shasum": "" }, "require": { @@ -8829,7 +8769,7 @@ ], "support": { "issues": "https://github.com/yajra/laravel-datatables/issues", - "source": "https://github.com/yajra/laravel-datatables/tree/v11.1.3" + "source": "https://github.com/yajra/laravel-datatables/tree/v11.1.5" }, "funding": [ { @@ -8837,29 +8777,29 @@ "type": "github" } ], - "time": "2024-07-15T04:47:52+00:00" + "time": "2024-08-26T01:43:52+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.13.5", + "version": "v3.14.7", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07" + "reference": "f484b8c9124de0b163da39958331098ffcd4a65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/92d86be45ee54edff735e46856f64f14b6a8bb07", - "reference": "92d86be45ee54edff735e46856f64f14b6a8bb07", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f484b8c9124de0b163da39958331098ffcd4a65e", + "reference": "f484b8c9124de0b163da39958331098ffcd4a65e", "shasum": "" }, "require": { "illuminate/routing": "^9|^10|^11", "illuminate/session": "^9|^10|^11", "illuminate/support": "^9|^10|^11", - "maximebf/debugbar": "~1.22.0", + "maximebf/debugbar": "~1.23.0", "php": "^8.0", "symfony/finder": "^6|^7" }, @@ -8872,7 +8812,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.13-dev" + "dev-master": "3.14-dev" }, "laravel": { "providers": [ @@ -8911,7 +8851,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.5" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.7" }, "funding": [ { @@ -8923,20 +8863,20 @@ "type": "github" } ], - "time": "2024-04-12T11:20:37+00:00" + "time": "2024-11-14T09:12:35+00:00" }, { "name": "fakerphp/faker", - "version": "v1.23.1", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + "reference": "a136842a532bac9ecd8a1c723852b09915d7db50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50", + "reference": "a136842a532bac9ecd8a1c723852b09915d7db50", "shasum": "" }, "require": { @@ -8984,32 +8924,32 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0" }, - "time": "2024-01-02T13:46:09+00:00" + "time": "2024-11-07T15:11:20+00:00" }, { "name": "filp/whoops", - "version": "2.15.4", + "version": "2.16.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", + "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2", + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", + "php": "^7.1 || ^8.0", "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -9049,7 +8989,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.4" + "source": "https://github.com/filp/whoops/tree/2.16.0" }, "funding": [ { @@ -9057,7 +8997,7 @@ "type": "github" } ], - "time": "2023-11-03T12:00:00+00:00" + "time": "2024-09-25T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -9112,16 +9052,16 @@ }, { "name": "laravel/sail", - "version": "v1.31.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "48d89608a3bb5be763c9bb87121d31e7da27c1cb" + "reference": "d17abae06661dd6c46d13627b1683a2924259145" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/48d89608a3bb5be763c9bb87121d31e7da27c1cb", - "reference": "48d89608a3bb5be763c9bb87121d31e7da27c1cb", + "url": "https://api.github.com/repos/laravel/sail/zipball/d17abae06661dd6c46d13627b1683a2924259145", + "reference": "d17abae06661dd6c46d13627b1683a2924259145", "shasum": "" }, "require": { @@ -9171,20 +9111,20 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2024-07-22T14:36:50+00:00" + "time": "2024-11-11T20:16:51+00:00" }, { "name": "maximebf/debugbar", - "version": "v1.22.3", + "version": "v1.23.3", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96" + "reference": "687400043d77943ef95e8417cb44e1673ee57844" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", - "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/687400043d77943ef95e8417cb44e1673ee57844", + "reference": "687400043d77943ef95e8417cb44e1673ee57844", "shasum": "" }, "require": { @@ -9206,7 +9146,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.22-dev" + "dev-master": "1.23-dev" } }, "autoload": { @@ -9237,9 +9177,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.3" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.3" }, - "time": "2024-04-03T19:39:26+00:00" + "time": "2024-10-29T12:24:25+00:00" }, { "name": "mockery/mockery", @@ -9326,16 +9266,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -9374,7 +9314,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -9382,27 +9322,27 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nunomaduro/collision", - "version": "v8.3.0", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "b49f5b2891ce52726adfd162841c69d4e4c84229" + "reference": "f5c101b929c958e849a633283adff296ed5f38f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/b49f5b2891ce52726adfd162841c69d4e4c84229", - "reference": "b49f5b2891ce52726adfd162841c69d4e4c84229", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", + "reference": "f5c101b929c958e849a633283adff296ed5f38f5", "shasum": "" }, "require": { - "filp/whoops": "^2.15.4", - "nunomaduro/termwind": "^2.0.1", + "filp/whoops": "^2.16.0", + "nunomaduro/termwind": "^2.1.0", "php": "^8.2.0", - "symfony/console": "^7.1.2" + "symfony/console": "^7.1.5" }, "conflict": { "laravel/framework": "<11.0.0 || >=12.0.0", @@ -9410,14 +9350,14 @@ }, "require-dev": { "larastan/larastan": "^2.9.8", - "laravel/framework": "^11.16.0", - "laravel/pint": "^1.16.2", - "laravel/sail": "^1.30.2", - "laravel/sanctum": "^4.0.2", - "laravel/tinker": "^2.9.0", - "orchestra/testbench-core": "^9.2.1", - "pestphp/pest": "^2.34.9 || ^3.0.0", - "sebastian/environment": "^6.1.0 || ^7.0.0" + "laravel/framework": "^11.28.0", + "laravel/pint": "^1.18.1", + "laravel/sail": "^1.36.0", + "laravel/sanctum": "^4.0.3", + "laravel/tinker": "^2.10.0", + "orchestra/testbench-core": "^9.5.3", + "pestphp/pest": "^2.36.0 || ^3.4.0", + "sebastian/environment": "^6.1.0 || ^7.2.0" }, "type": "library", "extra": { @@ -9479,7 +9419,7 @@ "type": "patreon" } ], - "time": "2024-07-16T22:41:01+00:00" + "time": "2024-10-15T16:06:32+00:00" }, { "name": "phar-io/manifest", @@ -9601,35 +9541,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.5", + "version": "11.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861" + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/19b6365ab8b59a64438c0c3f4241feeb480c9861", - "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.0", + "nikic/php-parser": "^5.3.1", "php": ">=8.2", - "phpunit/php-file-iterator": "^5.0", - "phpunit/php-text-template": "^4.0", - "sebastian/code-unit-reverse-lookup": "^4.0", - "sebastian/complexity": "^4.0", - "sebastian/environment": "^7.0", - "sebastian/lines-of-code": "^3.0", - "sebastian/version": "^5.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.4.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -9638,7 +9578,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -9667,7 +9607,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" }, "funding": [ { @@ -9675,20 +9615,20 @@ "type": "github" } ], - "time": "2024-07-03T05:05:37+00:00" + "time": "2024-10-09T06:21:38+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "5.0.1", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26" + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6ed896bf50bbbfe4d504a33ed5886278c78e4a26", - "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", "shasum": "" }, "require": { @@ -9728,7 +9668,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" }, "funding": [ { @@ -9736,7 +9676,7 @@ "type": "github" } ], - "time": "2024-07-03T05:06:37+00:00" + "time": "2024-08-27T05:02:59+00:00" }, { "name": "phpunit/php-invoker", @@ -9924,16 +9864,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.2.8", + "version": "11.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39" + "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a7a29e8d3113806f18f99d670f580a30e8ffff39", - "reference": "a7a29e8d3113806f18f99d670f580a30e8ffff39", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e8e8ed1854de5d36c088ec1833beae40d2dedd76", + "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76", "shasum": "" }, "require": { @@ -9947,21 +9887,21 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.5", - "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-code-coverage": "^11.0.7", + "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.0.1", + "sebastian/comparator": "^6.1.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", "sebastian/exporter": "^6.1.3", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.0.1", - "sebastian/version": "^5.0.1" + "sebastian/type": "^5.1.0", + "sebastian/version": "^5.0.2" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -9972,7 +9912,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.2-dev" + "dev-main": "11.4-dev" } }, "autoload": { @@ -10004,7 +9944,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.8" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.3" }, "funding": [ { @@ -10020,7 +9960,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T14:56:37+00:00" + "time": "2024-10-28T13:07:50+00:00" }, { "name": "sebastian/cli-parser", @@ -10194,16 +10134,16 @@ }, { "name": "sebastian/comparator", - "version": "6.0.1", + "version": "6.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "131942b86d3587291067a94f295498ab6ac79c20" + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/131942b86d3587291067a94f295498ab6ac79c20", - "reference": "131942b86d3587291067a94f295498ab6ac79c20", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", "shasum": "" }, "require": { @@ -10214,12 +10154,12 @@ "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "6.2-dev" } }, "autoload": { @@ -10259,7 +10199,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" }, "funding": [ { @@ -10267,7 +10207,7 @@ "type": "github" } ], - "time": "2024-07-03T04:48:07+00:00" + "time": "2024-10-31T05:30:08+00:00" }, { "name": "sebastian/complexity", @@ -10836,28 +10776,28 @@ }, { "name": "sebastian/type", - "version": "5.0.1", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa" + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb6a6566f9589e86661291d13eba708cce5eb4aa", - "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -10881,7 +10821,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" }, "funding": [ { @@ -10889,20 +10829,20 @@ "type": "github" } ], - "time": "2024-07-03T05:11:49+00:00" + "time": "2024-09-17T13:12:04+00:00" }, { "name": "sebastian/version", - "version": "5.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/45c9debb7d039ce9b97de2f749c2cf5832a06ac4", - "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { @@ -10935,7 +10875,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -10943,7 +10883,7 @@ "type": "github" } ], - "time": "2024-07-03T05:13:08+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { "name": "spatie/backtrace", @@ -11084,16 +11024,16 @@ }, { "name": "spatie/flare-client-php", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234" + "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234", - "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122", + "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122", "shasum": "" }, "require": { @@ -11111,7 +11051,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/phpunit-snapshot-assertions": "^4.0|^5.0" + "spatie/pest-plugin-snapshots": "^1.0|^2.0" }, "type": "library", "extra": { @@ -11141,7 +11081,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.7.0" + "source": "https://github.com/spatie/flare-client-php/tree/1.8.0" }, "funding": [ { @@ -11149,7 +11089,7 @@ "type": "github" } ], - "time": "2024-06-12T14:39:14+00:00" + "time": "2024-08-01T08:27:26+00:00" }, { "name": "spatie/ignition", @@ -11327,16 +11267,16 @@ }, { "name": "symfony/yaml", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "fa34c77015aa6720469db7003567b9f772492bf2" + "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", - "reference": "fa34c77015aa6720469db7003567b9f772492bf2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/3ced3f29e4f0d6bce2170ff26719f1fe9aacc671", + "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671", "shasum": "" }, "require": { @@ -11378,7 +11318,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.1.1" + "source": "https://github.com/symfony/yaml/tree/v7.1.6" }, "funding": [ { @@ -11394,7 +11334,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "theseer/tokenizer", @@ -11462,5 +11402,5 @@ "platform-overrides": { "php": "8.2" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } From 06113b30966e97f881003eb7ce05db5efa0823fd Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 19 Nov 2024 09:12:21 +0100 Subject: [PATCH 510/514] Update verification Email --- app/Models/User.php | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/app/Models/User.php b/app/Models/User.php index 9577fa808..350e73bd0 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -14,6 +14,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\RateLimiter; use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\Traits\CausesActivity; @@ -189,18 +190,23 @@ public function discordUser() public function sendEmailVerificationNotification() { - // Rate limit the email verification notification to 1 attempt per 30 minutes - $executed = RateLimiter::attempt( - key: 'verify-mail'. $this->id, - maxAttempts: 1, - callback: function() { - $this->notify(new QueuedVerifyEmail); - }, - decaySeconds: 1800 - ); - - if (! $executed) { - return response()->json(['message' => 'Too many requests, try again in: ' . RateLimiter::availableIn('verify-mail:'. $this->id) . ' seconds'], 429); + try { + // Rate limit the email verification notification to 5 attempt per 30 minutes + $executed = RateLimiter::attempt( + key: 'verify-mail' . $this->id, + maxAttempts: 5, + callback: function () { + $this->notify(new QueuedVerifyEmail); + }, + decaySeconds: 1800 + ); + + if (!$executed) { + return redirect()->back()->with('error', 'Too many requests. Try again in ' . RateLimiter::availableIn('verify-mail:' . $this->id) . ' seconds.'); + } + }catch (\Exception $exception){ + Log::error($exception->getMessage()); + return redirect()->back()->with('error', __("Something went wrong. Please try again later!")); } } From a6055e051772082c22f4a98d0255fdd50d3b6880 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Tue, 19 Nov 2024 09:19:13 +0100 Subject: [PATCH 511/514] [Fix]} Update Serverdestroy Usercheck --- app/Http/Controllers/ServerController.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index 7f5919b4f..d7b1c833c 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -285,6 +285,9 @@ private function serverCreationFailed(Response $response, Server $server) /** Remove the specified resource from storage. */ public function destroy(Server $server, DiscordSettings $discord_settings) { + if ($server->user_id != Auth::user()->id) { + return back()->with('error', __('This is not your Server!')); + } try { // Remove role from discord try { From d8bff263ef4961ef8d80d3bdaf62b1df93c3b266 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 21 Nov 2024 18:23:11 +0100 Subject: [PATCH 512/514] [Fix] Free server Suspend --- app/Console/Commands/ChargeCreditsCommand.php | 101 ------------------ app/Console/Commands/ChargeServers.php | 2 +- 2 files changed, 1 insertion(+), 102 deletions(-) delete mode 100644 app/Console/Commands/ChargeCreditsCommand.php diff --git a/app/Console/Commands/ChargeCreditsCommand.php b/app/Console/Commands/ChargeCreditsCommand.php deleted file mode 100644 index 145943c7e..000000000 --- a/app/Console/Commands/ChargeCreditsCommand.php +++ /dev/null @@ -1,101 +0,0 @@ -<?php - -namespace App\Console\Commands; - -use App\Models\Product; -use App\Models\Server; -use App\Models\User; -use App\Notifications\ServersSuspendedNotification; -use Illuminate\Console\Command; - -class ChargeCreditsCommand extends Command -{ - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'credits:charge'; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Charge all users with active servers'; - - /** - * A list of users that have to be notified - * - * @var array - */ - protected $usersToNotify = []; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Execute the console command. - * - * @return string - */ - public function handle() - { - Server::whereNull('suspended')->chunk(10, function ($servers) { - /** @var Server $server */ - foreach ($servers as $server) { - /** @var Product $product */ - $product = $server->product; - /** @var User $user */ - $user = $server->user; - - //charge credits / suspend server - if ($user->credits >= $product->getHourlyPrice()) { - $this->line("<fg=blue>{$user->name}</> Current credits: <fg=green>{$user->credits}</> Credits to be removed: <fg=red>{$product->getHourlyPrice()}</>"); - $user->decrement('credits', $product->getHourlyPrice()); - } else { - try { - //suspend server - $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); - $server->suspend(); - - //add user to notify list - if (!in_array($user, $this->usersToNotify)) { - array_push($this->usersToNotify, $user); - } - } catch (\Exception $exception) { - $this->error($exception->getMessage()); - } - } - } - }); - - return $this->notifyUsers(); - } - - /** - * @return bool - */ - public function notifyUsers() - { - if (!empty($this->usersToNotify)) { - /** @var User $user */ - foreach ($this->usersToNotify as $user) { - $this->line("<fg=yellow>Notified user:</> <fg=blue>{$user->name}</>"); - $user->notify(new ServersSuspendedNotification()); - } - } - - //reset array - $this->usersToNotify = []; - - return true; - } -} diff --git a/app/Console/Commands/ChargeServers.php b/app/Console/Commands/ChargeServers.php index 8c7b9d4de..f623b8ec2 100644 --- a/app/Console/Commands/ChargeServers.php +++ b/app/Console/Commands/ChargeServers.php @@ -91,7 +91,7 @@ public function handle() } // check if the server is canceled or if user has enough credits to charge the server or - if ($server->canceled || $user->credits <= $product->price) { + if ($server->canceled || ($user->credits <= $product->price && $product->price != 0 )) { try { // suspend server $this->line("<fg=yellow>{$server->name}</> from user: <fg=blue>{$user->name}</> has been <fg=red>suspended!</>"); From 6e746fad072972c9a80a6b0d2abd987f8e9fb171 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 21 Nov 2024 18:31:55 +0100 Subject: [PATCH 513/514] Update Kernel.php --- app/Console/Kernel.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index a1bd37d22..a271e673d 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -14,7 +14,6 @@ class Kernel extends ConsoleKernel * @var array */ protected $commands = [ - Commands\ChargeCreditsCommand::class, Commands\ChargeServers::class, Commands\DeleteExpiredCoupons::class, ]; From 2d2e1e0cc7a457365da4e0683c1459918f9b5625 Mon Sep 17 00:00:00 2001 From: 1day2die <ownerdennis8@gmail.com> Date: Thu, 21 Nov 2024 18:56:28 +0100 Subject: [PATCH 514/514] [TempFix] Discord ID on USertable --- app/Http/Controllers/Admin/UserController.php | 3 +++ themes/default/views/admin/users/index.blade.php | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 3cc7709f9..cfa02b256 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -398,9 +398,12 @@ public function dataTable(Request $request) ->addColumn('verified', function (User $user) { return $user->getVerifiedStatus(); }) + /* This broke the ability to search the table. Have to revisit later + ->addColumn('discordId', function (User $user) { return $user->discordUser ? $user->discordUser->id : ''; }) + */ ->addColumn('actions', function (User $user) { $suspendColor = $user->isSuspended() ? 'btn-success' : 'btn-warning'; $suspendIcon = $user->isSuspended() ? 'fa-play-circle' : 'fa-pause-circle'; diff --git a/themes/default/views/admin/users/index.blade.php b/themes/default/views/admin/users/index.blade.php index 69d9d3dc8..9accacb9a 100644 --- a/themes/default/views/admin/users/index.blade.php +++ b/themes/default/views/admin/users/index.blade.php @@ -84,11 +84,15 @@ function submitResult() { order: [ [11, "desc"] ], - columns: [{ + columns: [ + /* This broke the ability to search the table. Have to revisit later + { data: 'discordId', visible: false, name: 'discordUser.id' }, + */ + { data: 'pterodactyl_id', visible: false