root 3 years ago
parent
commit
637abfc4ad
100 changed files with 14520 additions and 894 deletions
  1. 1 0
      .htaccess
  2. 1 0
      .user.ini
  3. 2 1
      composer.json
  4. 429 2
      composer.lock
  5. 1 3
      vendor/composer/ClassLoader.php
  6. 898 326
      vendor/composer/InstalledVersions.php
  7. 68 0
      vendor/composer/autoload_classmap.php
  8. 3 1
      vendor/composer/autoload_files.php
  9. 5 0
      vendor/composer/autoload_psr4.php
  10. 99 1
      vendor/composer/autoload_static.php
  11. 448 0
      vendor/composer/installed.json
  12. 632 558
      vendor/composer/installed.php
  13. 43 0
      vendor/nette/php-generator/composer.json
  14. 60 0
      vendor/nette/php-generator/license.md
  15. 736 0
      vendor/nette/php-generator/readme.md
  16. 49 0
      vendor/nette/php-generator/src/PhpGenerator/Attribute.php
  17. 627 0
      vendor/nette/php-generator/src/PhpGenerator/ClassType.php
  18. 72 0
      vendor/nette/php-generator/src/PhpGenerator/Closure.php
  19. 59 0
      vendor/nette/php-generator/src/PhpGenerator/Constant.php
  20. 240 0
      vendor/nette/php-generator/src/PhpGenerator/Dumper.php
  21. 41 0
      vendor/nette/php-generator/src/PhpGenerator/EnumCase.php
  22. 411 0
      vendor/nette/php-generator/src/PhpGenerator/Factory.php
  23. 52 0
      vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php
  24. 108 0
      vendor/nette/php-generator/src/PhpGenerator/Helpers.php
  25. 34 0
      vendor/nette/php-generator/src/PhpGenerator/Literal.php
  26. 143 0
      vendor/nette/php-generator/src/PhpGenerator/Method.php
  27. 134 0
      vendor/nette/php-generator/src/PhpGenerator/Parameter.php
  28. 138 0
      vendor/nette/php-generator/src/PhpGenerator/PhpFile.php
  29. 15 0
      vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php
  30. 217 0
      vendor/nette/php-generator/src/PhpGenerator/PhpNamespace.php
  31. 366 0
      vendor/nette/php-generator/src/PhpGenerator/Printer.php
  32. 37 0
      vendor/nette/php-generator/src/PhpGenerator/PromotedParameter.php
  33. 134 0
      vendor/nette/php-generator/src/PhpGenerator/Property.php
  34. 23 0
      vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php
  35. 49 0
      vendor/nette/php-generator/src/PhpGenerator/Traits/AttributeAware.php
  36. 42 0
      vendor/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php
  37. 186 0
      vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php
  38. 49 0
      vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php
  39. 85 0
      vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php
  40. 73 0
      vendor/nette/php-generator/src/PhpGenerator/Type.php
  41. 19 0
      vendor/nette/utils/.phpstorm.meta.php
  42. 50 0
      vendor/nette/utils/composer.json
  43. 33 0
      vendor/nette/utils/contributing.md
  44. 60 0
      vendor/nette/utils/license.md
  45. 53 0
      vendor/nette/utils/readme.md
  46. 22 0
      vendor/nette/utils/src/HtmlStringable.php
  47. 166 0
      vendor/nette/utils/src/Iterators/CachingIterator.php
  48. 34 0
      vendor/nette/utils/src/Iterators/Mapper.php
  49. 122 0
      vendor/nette/utils/src/SmartObject.php
  50. 34 0
      vendor/nette/utils/src/StaticClass.php
  51. 27 0
      vendor/nette/utils/src/Translator.php
  52. 97 0
      vendor/nette/utils/src/Utils/ArrayHash.php
  53. 113 0
      vendor/nette/utils/src/Utils/ArrayList.php
  54. 420 0
      vendor/nette/utils/src/Utils/Arrays.php
  55. 181 0
      vendor/nette/utils/src/Utils/Callback.php
  56. 144 0
      vendor/nette/utils/src/Utils/DateTime.php
  57. 210 0
      vendor/nette/utils/src/Utils/FileSystem.php
  58. 107 0
      vendor/nette/utils/src/Utils/Floats.php
  59. 71 0
      vendor/nette/utils/src/Utils/Helpers.php
  60. 875 0
      vendor/nette/utils/src/Utils/Html.php
  61. 726 0
      vendor/nette/utils/src/Utils/Image.php
  62. 64 0
      vendor/nette/utils/src/Utils/Json.php
  63. 212 0
      vendor/nette/utils/src/Utils/ObjectHelpers.php
  64. 41 0
      vendor/nette/utils/src/Utils/ObjectMixin.php
  65. 242 0
      vendor/nette/utils/src/Utils/Paginator.php
  66. 45 0
      vendor/nette/utils/src/Utils/Random.php
  67. 417 0
      vendor/nette/utils/src/Utils/Reflection.php
  68. 550 0
      vendor/nette/utils/src/Utils/Strings.php
  69. 373 0
      vendor/nette/utils/src/Utils/Validators.php
  70. 58 0
      vendor/nette/utils/src/Utils/exceptions.php
  71. 32 0
      vendor/nette/utils/src/compatibility.php
  72. 109 0
      vendor/nette/utils/src/exceptions.php
  73. 4 0
      vendor/open-smf/connection-pool/.gitignore
  74. 21 0
      vendor/open-smf/connection-pool/LICENSE
  75. 223 0
      vendor/open-smf/connection-pool/README.md
  76. 40 0
      vendor/open-smf/connection-pool/composer.json
  77. 48 0
      vendor/open-smf/connection-pool/examples/coroutine-mysql.php
  78. 41 0
      vendor/open-smf/connection-pool/examples/coroutine-postgresql.php
  79. 48 0
      vendor/open-smf/connection-pool/examples/coroutine-redis.php
  80. 49 0
      vendor/open-smf/connection-pool/examples/coroutine-runtime-pdo.php
  81. 47 0
      vendor/open-smf/connection-pool/examples/coroutine-runtime-phpredis.php
  82. 65 0
      vendor/open-smf/connection-pool/examples/dynamic-testing.php
  83. 132 0
      vendor/open-smf/connection-pool/examples/http-server.php
  84. 19 0
      vendor/open-smf/connection-pool/src/BorrowConnectionTimeoutException.php
  85. 279 0
      vendor/open-smf/connection-pool/src/ConnectionPool.php
  86. 33 0
      vendor/open-smf/connection-pool/src/ConnectionPoolInterface.php
  87. 51 0
      vendor/open-smf/connection-pool/src/ConnectionPoolTrait.php
  88. 43 0
      vendor/open-smf/connection-pool/src/Connectors/ConnectorInterface.php
  89. 39 0
      vendor/open-smf/connection-pool/src/Connectors/CoroutineMySQLConnector.php
  90. 42 0
      vendor/open-smf/connection-pool/src/Connectors/CoroutinePostgreSQLConnector.php
  91. 53 0
      vendor/open-smf/connection-pool/src/Connectors/CoroutineRedisConnector.php
  92. 42 0
      vendor/open-smf/connection-pool/src/Connectors/PDOConnector.php
  93. 53 0
      vendor/open-smf/connection-pool/src/Connectors/PhpRedisConnector.php
  94. 3 2
      vendor/services.php
  95. 3 0
      vendor/stechstudio/backoff/.gitignore
  96. 21 0
      vendor/stechstudio/backoff/LICENSE.md
  97. 216 0
      vendor/stechstudio/backoff/README.md
  98. 23 0
      vendor/stechstudio/backoff/composer.json
  99. 17 0
      vendor/stechstudio/backoff/phpunit.xml
  100. 348 0
      vendor/stechstudio/backoff/src/Backoff.php

+ 1 - 0
.htaccess

@@ -0,0 +1 @@
+ 

+ 1 - 0
.user.ini

@@ -0,0 +1 @@
+open_basedir=/www/wwwroot/myc.frp.liuniu946.com/:/tmp/

+ 2 - 1
composer.json

@@ -30,7 +30,8 @@
         "xaboy/form-builder": "^2.0",
         "phpoffice/phpexcel": "^1.8",
         "endroid/qr-code": "^4.3",
-        "aliyuncs/oss-sdk-php": "^2.4"
+        "aliyuncs/oss-sdk-php": "^2.4",
+        "topthink/think-swoole": "^4.0"
     },
     "require-dev": {
         "symfony/var-dumper": "^4.2",

+ 429 - 2
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": "7bdd1cd4c7b54a82528656dcdd995a0a",
+    "content-hash": "3cf337e6008a194961176e6f1f9ce340",
     "packages": [
         {
             "name": "aliyuncs/oss-sdk-php",
@@ -1205,6 +1205,213 @@
             ],
             "time": "2021-09-06T09:29:23+00:00"
         },
+        {
+            "name": "nette/php-generator",
+            "version": "v3.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/php-generator.git",
+                "reference": "2f28a34203ea2e730371d913fc06ae45b6e9baaa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/php-generator/zipball/2f28a34203ea2e730371d913fc06ae45b6e9baaa",
+                "reference": "2f28a34203ea2e730371d913fc06ae45b6e9baaa",
+                "shasum": ""
+            },
+            "require": {
+                "nette/utils": "^3.1.2",
+                "php": ">=7.2 <8.2"
+            },
+            "require-dev": {
+                "nette/tester": "^2.0",
+                "nikic/php-parser": "^4.4",
+                "phpstan/phpstan": "^0.12",
+                "tracy/tracy": "^2.3"
+            },
+            "suggest": {
+                "nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.6-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "code",
+                "nette",
+                "php",
+                "scaffolding"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/php-generator/issues",
+                "source": "https://github.com/nette/php-generator/tree/v3.6.0"
+            },
+            "time": "2021-08-29T15:43:46+00:00"
+        },
+        {
+            "name": "nette/utils",
+            "version": "v3.2.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/utils.git",
+                "reference": "5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/utils/zipball/5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822",
+                "reference": "5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2 <8.1"
+            },
+            "conflict": {
+                "nette/di": "<3.0.6"
+            },
+            "require-dev": {
+                "nette/tester": "~2.0",
+                "phpstan/phpstan": "^0.12",
+                "tracy/tracy": "^2.3"
+            },
+            "suggest": {
+                "ext-gd": "to use Image",
+                "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+                "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"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "🛠  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "array",
+                "core",
+                "datetime",
+                "images",
+                "json",
+                "nette",
+                "paginator",
+                "password",
+                "slugify",
+                "string",
+                "unicode",
+                "utf-8",
+                "utility",
+                "validation"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/utils/issues",
+                "source": "https://github.com/nette/utils/tree/v3.2.3"
+            },
+            "time": "2021-08-16T21:05:00+00:00"
+        },
+        {
+            "name": "open-smf/connection-pool",
+            "version": "v1.0.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/open-smf/connection-pool.git",
+                "reference": "f70e47dbf56f1869d3207e15825cf38810b865e0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/open-smf/connection-pool/zipball/f70e47dbf56f1869d3207e15825cf38810b865e0",
+                "reference": "f70e47dbf56f1869d3207e15825cf38810b865e0",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-swoole": ">=4.2.9",
+                "php": ">=7.0.0"
+            },
+            "require-dev": {
+                "swoole/ide-helper": "@dev"
+            },
+            "suggest": {
+                "ext-redis": "A PHP extension for Redis."
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Smf\\ConnectionPool\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Xie Biao",
+                    "email": "hhxsv5@sina.com"
+                }
+            ],
+            "description": "A common connection pool based on Swoole is usually used as the database connection pool.",
+            "homepage": "https://github.com/open-smf/connection-pool",
+            "keywords": [
+                "connection-pool",
+                "database-connection-pool",
+                "swoole"
+            ],
+            "support": {
+                "issues": "https://github.com/open-smf/connection-pool/issues",
+                "source": "https://github.com/open-smf/connection-pool"
+            },
+            "time": "2021-03-01T04:13:24+00:00"
+        },
         {
             "name": "overtrue/socialite",
             "version": "3.2.4",
@@ -2076,6 +2283,97 @@
             },
             "time": "2019-03-08T08:55:37+00:00"
         },
+        {
+            "name": "stechstudio/backoff",
+            "version": "1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/stechstudio/backoff.git",
+                "reference": "816e46107a6be2e1072ba0ff2cb26034872dfa49"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/stechstudio/backoff/zipball/816e46107a6be2e1072ba0ff2cb26034872dfa49",
+                "reference": "816e46107a6be2e1072ba0ff2cb26034872dfa49",
+                "shasum": ""
+            },
+            "require-dev": {
+                "phpunit/phpunit": "5.5.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "STS\\Backoff\\": "src"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Joseph Szobody",
+                    "email": "joseph@stechstudio.com"
+                }
+            ],
+            "description": "PHP library providing retry functionality with multiple backoff strategies and jitter support",
+            "support": {
+                "issues": "https://github.com/stechstudio/backoff/issues",
+                "source": "https://github.com/stechstudio/backoff/tree/1.2"
+            },
+            "time": "2020-12-26T14:57:10+00:00"
+        },
+        {
+            "name": "swoole/ide-helper",
+            "version": "4.7.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/swoole/ide-helper.git",
+                "reference": "918a98b5b264425fdb59461d9bbd7f9b504ead71"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/swoole/ide-helper/zipball/918a98b5b264425fdb59461d9bbd7f9b504ead71",
+                "reference": "918a98b5b264425fdb59461d9bbd7f9b504ead71",
+                "shasum": ""
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "~6.5.0",
+                "laminas/laminas-code": "~3.4.0",
+                "squizlabs/php_codesniffer": "~3.5.0",
+                "symfony/filesystem": "~4.0"
+            },
+            "type": "library",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Team Swoole",
+                    "email": "team@swoole.com"
+                }
+            ],
+            "description": "IDE help files for Swoole.",
+            "support": {
+                "issues": "https://github.com/swoole/ide-helper/issues",
+                "source": "https://github.com/swoole/ide-helper/tree/4.7.1"
+            },
+            "funding": [
+                {
+                    "url": "https://gitee.com/swoole/swoole?donate=true",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/swoole",
+                    "type": "github"
+                }
+            ],
+            "time": "2021-08-19T17:25:57+00:00"
+        },
         {
             "name": "symfony/cache",
             "version": "v5.3.7",
@@ -2513,6 +2811,68 @@
             ],
             "time": "2021-03-23T23:28:01+00:00"
         },
+        {
+            "name": "symfony/finder",
+            "version": "v5.3.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/a10000ada1e600d109a6c7632e9ac42e8bf2fb93",
+                "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Finds files and directories via an intuitive fluent interface",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/finder/tree/v5.3.7"
+            },
+            "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": "2021-08-04T21:20:46+00:00"
+        },
         {
             "name": "symfony/http-foundation",
             "version": "v5.3.7",
@@ -3611,6 +3971,73 @@
             },
             "time": "2021-06-24T18:03:45+00:00"
         },
+        {
+            "name": "topthink/think-swoole",
+            "version": "v4.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-swoole.git",
+                "reference": "8118fc0422c7a57bc69ce2e505739671654667a3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-swoole/zipball/8118fc0422c7a57bc69ce2e505739671654667a3",
+                "reference": "8118fc0422c7a57bc69ce2e505739671654667a3",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-swoole": ">=4.6",
+                "nette/php-generator": "^3.2",
+                "open-smf/connection-pool": "~1.0",
+                "php": ">=7.4",
+                "stechstudio/backoff": "^1.2",
+                "swoole/ide-helper": "^4.3",
+                "symfony/finder": "^4.3.2|^5.1",
+                "topthink/framework": "^6.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5",
+                "symfony/var-dumper": "^4.3|^5.1",
+                "topthink/think-queue": "^3.0",
+                "topthink/think-tracing": "^1.0"
+            },
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "think\\swoole\\Service"
+                    ],
+                    "config": {
+                        "swoole": "src/config/swoole.php"
+                    }
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "think\\swoole\\": "src"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "Swoole extend for thinkphp",
+            "support": {
+                "issues": "https://github.com/top-think/think-swoole/issues",
+                "source": "https://github.com/top-think/think-swoole/tree/v4.0.5"
+            },
+            "time": "2021-09-03T05:40:24+00:00"
+        },
         {
             "name": "xaboy/form-builder",
             "version": "2.0.19",
@@ -3915,5 +4342,5 @@
         "php": ">=7.1.0"
     },
     "platform-dev": [],
-    "plugin-api-version": "2.1.0"
+    "plugin-api-version": "2.0.0"
 }

+ 1 - 3
vendor/composer/ClassLoader.php

@@ -338,7 +338,7 @@ class ClassLoader
      * Loads the given class or interface.
      *
      * @param  string    $class The name of the class
-     * @return true|null True if loaded, null otherwise
+     * @return bool|null True if loaded, null otherwise
      */
     public function loadClass($class)
     {
@@ -347,8 +347,6 @@ class ClassLoader
 
             return true;
         }
-
-        return null;
     }
 
     /**

+ 898 - 326
vendor/composer/InstalledVersions.php

@@ -1,337 +1,909 @@
 <?php
 
-/*
- * This file is part of Composer.
- *
- * (c) Nils Adermann <naderman@naderman.de>
- *     Jordi Boggiano <j.boggiano@seld.be>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
+
+
+
+
+
+
+
+
+
 
 namespace Composer;
 
 use Composer\Autoload\ClassLoader;
 use Composer\Semver\VersionParser;
 
-/**
- * This class is copied in every Composer installed project and available to all
- *
- * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
- *
- * To require it's presence, you can require `composer-runtime-api ^2.0`
- */
+
+
+
+
+
+
+
 class InstalledVersions
 {
-    private static $installed;
-    private static $canGetVendors;
-    private static $installedByVendor = array();
-
-    /**
-     * Returns a list of all package names which are present, either by being installed, replaced or provided
-     *
-     * @return string[]
-     * @psalm-return list<string>
-     */
-    public static function getInstalledPackages()
-    {
-        $packages = array();
-        foreach (self::getInstalled() as $installed) {
-            $packages[] = array_keys($installed['versions']);
-        }
-
-        if (1 === \count($packages)) {
-            return $packages[0];
-        }
-
-        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
-    }
-
-    /**
-     * Returns a list of all package names with a specific type e.g. 'library'
-     *
-     * @param  string   $type
-     * @return string[]
-     * @psalm-return list<string>
-     */
-    public static function getInstalledPackagesByType($type)
-    {
-        $packagesByType = array();
-
-        foreach (self::getInstalled() as $installed) {
-            foreach ($installed['versions'] as $name => $package) {
-                if (isset($package['type']) && $package['type'] === $type) {
-                    $packagesByType[] = $name;
-                }
-            }
-        }
-
-        return $packagesByType;
-    }
-
-    /**
-     * Checks whether the given package is installed
-     *
-     * This also returns true if the package name is provided or replaced by another package
-     *
-     * @param  string $packageName
-     * @param  bool   $includeDevRequirements
-     * @return bool
-     */
-    public static function isInstalled($packageName, $includeDevRequirements = true)
-    {
-        foreach (self::getInstalled() as $installed) {
-            if (isset($installed['versions'][$packageName])) {
-                return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Checks whether the given package satisfies a version constraint
-     *
-     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
-     *
-     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
-     *
-     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
-     * @param  string        $packageName
-     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
-     * @return bool
-     */
-    public static function satisfies(VersionParser $parser, $packageName, $constraint)
-    {
-        $constraint = $parser->parseConstraints($constraint);
-        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
-
-        return $provided->matches($constraint);
-    }
-
-    /**
-     * Returns a version constraint representing all the range(s) which are installed for a given package
-     *
-     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
-     * whether a given version of a package is installed, and not just whether it exists
-     *
-     * @param  string $packageName
-     * @return string Version constraint usable with composer/semver
-     */
-    public static function getVersionRanges($packageName)
-    {
-        foreach (self::getInstalled() as $installed) {
-            if (!isset($installed['versions'][$packageName])) {
-                continue;
-            }
-
-            $ranges = array();
-            if (isset($installed['versions'][$packageName]['pretty_version'])) {
-                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
-            }
-            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
-                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
-            }
-            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
-                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
-            }
-            if (array_key_exists('provided', $installed['versions'][$packageName])) {
-                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
-            }
-
-            return implode(' || ', $ranges);
-        }
-
-        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
-    }
-
-    /**
-     * @param  string      $packageName
-     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
-     */
-    public static function getVersion($packageName)
-    {
-        foreach (self::getInstalled() as $installed) {
-            if (!isset($installed['versions'][$packageName])) {
-                continue;
-            }
-
-            if (!isset($installed['versions'][$packageName]['version'])) {
-                return null;
-            }
-
-            return $installed['versions'][$packageName]['version'];
-        }
-
-        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
-    }
-
-    /**
-     * @param  string      $packageName
-     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
-     */
-    public static function getPrettyVersion($packageName)
-    {
-        foreach (self::getInstalled() as $installed) {
-            if (!isset($installed['versions'][$packageName])) {
-                continue;
-            }
-
-            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
-                return null;
-            }
-
-            return $installed['versions'][$packageName]['pretty_version'];
-        }
-
-        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
-    }
-
-    /**
-     * @param  string      $packageName
-     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
-     */
-    public static function getReference($packageName)
-    {
-        foreach (self::getInstalled() as $installed) {
-            if (!isset($installed['versions'][$packageName])) {
-                continue;
-            }
-
-            if (!isset($installed['versions'][$packageName]['reference'])) {
-                return null;
-            }
-
-            return $installed['versions'][$packageName]['reference'];
-        }
-
-        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
-    }
-
-    /**
-     * @param  string      $packageName
-     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
-     */
-    public static function getInstallPath($packageName)
-    {
-        foreach (self::getInstalled() as $installed) {
-            if (!isset($installed['versions'][$packageName])) {
-                continue;
-            }
-
-            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
-        }
-
-        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
-    }
-
-    /**
-     * @return array
-     * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}
-     */
-    public static function getRootPackage()
-    {
-        $installed = self::getInstalled();
-
-        return $installed[0]['root'];
-    }
-
-    /**
-     * Returns the raw installed.php data for custom implementations
-     *
-     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
-     * @return array[]
-     * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}
-     */
-    public static function getRawData()
-    {
-        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
-
-        if (null === self::$installed) {
-            // only require the installed.php file if this file is loaded from its dumped location,
-            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
-            if (substr(__DIR__, -8, 1) !== 'C') {
-                self::$installed = include __DIR__ . '/installed.php';
-            } else {
-                self::$installed = array();
-            }
-        }
-
-        return self::$installed;
-    }
-
-    /**
-     * Returns the raw data of all installed.php which are currently loaded for custom implementations
-     *
-     * @return array[]
-     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
-     */
-    public static function getAllRawData()
-    {
-        return self::getInstalled();
-    }
-
-    /**
-     * Lets you reload the static array from another file
-     *
-     * This is only useful for complex integrations in which a project needs to use
-     * this class but then also needs to execute another project's autoloader in process,
-     * and wants to ensure both projects have access to their version of installed.php.
-     *
-     * A typical case would be PHPUnit, where it would need to make sure it reads all
-     * the data it needs from this class, then call reload() with
-     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
-     * the project in which it runs can then also use this class safely, without
-     * interference between PHPUnit's dependencies and the project's dependencies.
-     *
-     * @param  array[] $data A vendor/composer/installed.php data set
-     * @return void
-     *
-     * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data
-     */
-    public static function reload($data)
-    {
-        self::$installed = $data;
-        self::$installedByVendor = array();
-    }
-
-    /**
-     * @return array[]
-     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
-     */
-    private static function getInstalled()
-    {
-        if (null === self::$canGetVendors) {
-            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
-        }
-
-        $installed = array();
-
-        if (self::$canGetVendors) {
-            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
-                if (isset(self::$installedByVendor[$vendorDir])) {
-                    $installed[] = self::$installedByVendor[$vendorDir];
-                } elseif (is_file($vendorDir.'/composer/installed.php')) {
-                    $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
-                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
-                        self::$installed = $installed[count($installed) - 1];
-                    }
-                }
-            }
-        }
-
-        if (null === self::$installed) {
-            // only require the installed.php file if this file is loaded from its dumped location,
-            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
-            if (substr(__DIR__, -8, 1) !== 'C') {
-                self::$installed = require __DIR__ . '/installed.php';
-            } else {
-                self::$installed = array();
-            }
-        }
-        $installed[] = self::$installed;
-
-        return $installed;
-    }
+private static $installed = array (
+  'root' => 
+  array (
+    'pretty_version' => 'dev-master',
+    'version' => 'dev-master',
+    'aliases' => 
+    array (
+    ),
+    'reference' => '05fff7683f644fab2c795b060cb71686f72d52fb',
+    'name' => 'topthink/think',
+  ),
+  'versions' => 
+  array (
+    'aliyuncs/oss-sdk-php' => 
+    array (
+      'pretty_version' => 'v2.4.3',
+      'version' => '2.4.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '4ccead614915ee6685bf30016afb01aabd347e46',
+    ),
+    'bacon/bacon-qr-code' => 
+    array (
+      'pretty_version' => '2.0.4',
+      'version' => '2.0.4.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f73543ac4e1def05f1a70bcd1525c8a157a1ad09',
+    ),
+    'dasprid/enum' => 
+    array (
+      'pretty_version' => '1.0.3',
+      'version' => '1.0.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5abf82f213618696dda8e3bf6f64dd042d8542b2',
+    ),
+    'doctrine/annotations' => 
+    array (
+      'pretty_version' => '1.13.2',
+      'version' => '1.13.2.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5b668aef16090008790395c02c893b1ba13f7e08',
+    ),
+    'doctrine/lexer' => 
+    array (
+      'pretty_version' => '1.2.1',
+      'version' => '1.2.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042',
+    ),
+    'easywechat-composer/easywechat-composer' => 
+    array (
+      'pretty_version' => '1.4.1',
+      'version' => '1.4.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd',
+    ),
+    'endroid/qr-code' => 
+    array (
+      'pretty_version' => '4.3.0',
+      'version' => '4.3.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '98f6d4024289ad3a8d7f3e63cab947ef6929dcdb',
+    ),
+    'firebase/php-jwt' => 
+    array (
+      'pretty_version' => 'v5.4.0',
+      'version' => '5.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd2113d9b2e0e349796e72d2a63cf9319100382d2',
+    ),
+    'guzzlehttp/guzzle' => 
+    array (
+      'pretty_version' => '7.3.0',
+      'version' => '7.3.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '7008573787b430c1c1f650e3722d9bba59967628',
+    ),
+    'guzzlehttp/promises' => 
+    array (
+      'pretty_version' => '1.4.1',
+      'version' => '1.4.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '8e7d04f1f6450fef59366c399cfad4b9383aa30d',
+    ),
+    'guzzlehttp/psr7' => 
+    array (
+      'pretty_version' => '2.0.0',
+      'version' => '2.0.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '1dc8d9cba3897165e16d12bb13d813afb1eb3fe7',
+    ),
+    'league/flysystem' => 
+    array (
+      'pretty_version' => '1.1.5',
+      'version' => '1.1.5.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '18634df356bfd4119fe3d6156bdb990c414c14ea',
+    ),
+    'league/flysystem-cached-adapter' => 
+    array (
+      'pretty_version' => '1.1.0',
+      'version' => '1.1.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd1925efb2207ac4be3ad0c40b8277175f99ffaff',
+    ),
+    'league/mime-type-detection' => 
+    array (
+      'pretty_version' => '1.7.0',
+      'version' => '1.7.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3',
+    ),
+    'monolog/monolog' => 
+    array (
+      'pretty_version' => '2.3.2',
+      'version' => '2.3.2.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '71312564759a7db5b789296369c1a264efc43aad',
+    ),
+    'nesbot/carbon' => 
+    array (
+      'pretty_version' => '2.53.1',
+      'version' => '2.53.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f4655858a784988f880c1b8c7feabbf02dfdf045',
+    ),
+    'nette/php-generator' => 
+    array (
+      'pretty_version' => 'v3.6.0',
+      'version' => '3.6.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '2f28a34203ea2e730371d913fc06ae45b6e9baaa',
+    ),
+    'nette/utils' => 
+    array (
+      'pretty_version' => 'v3.2.3',
+      'version' => '3.2.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822',
+    ),
+    'open-smf/connection-pool' => 
+    array (
+      'pretty_version' => 'v1.0.16',
+      'version' => '1.0.16.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f70e47dbf56f1869d3207e15825cf38810b865e0',
+    ),
+    'overtrue/socialite' => 
+    array (
+      'pretty_version' => '3.2.4',
+      'version' => '3.2.4.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '7605f4370a92ea4811cc5016d4709ef3d545f51c',
+    ),
+    'overtrue/wechat' => 
+    array (
+      'pretty_version' => '5.7.2',
+      'version' => '5.7.2.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd57d2104fe705511374ae3627b0d9b7a761d87b9',
+    ),
+    'phpoffice/phpexcel' => 
+    array (
+      'pretty_version' => '1.8.1',
+      'version' => '1.8.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '372c7cbb695a6f6f1e62649381aeaa37e7e70b32',
+    ),
+    'pimple/pimple' => 
+    array (
+      'pretty_version' => 'v3.4.0',
+      'version' => '3.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '86406047271859ffc13424a048541f4531f53601',
+    ),
+    'psr/cache' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8',
+    ),
+    'psr/cache-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0|2.0',
+      ),
+    ),
+    'psr/container' => 
+    array (
+      'pretty_version' => '1.1.1',
+      'version' => '1.1.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf',
+    ),
+    'psr/event-dispatcher' => 
+    array (
+      'pretty_version' => '1.0.0',
+      'version' => '1.0.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
+    ),
+    'psr/event-dispatcher-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'psr/http-client' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
+    ),
+    'psr/http-client-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'psr/http-factory' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
+    ),
+    'psr/http-factory-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'psr/http-message' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
+    ),
+    'psr/http-message-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'psr/log' => 
+    array (
+      'pretty_version' => '1.1.4',
+      'version' => '1.1.4.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
+    ),
+    'psr/log-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0.0',
+      ),
+    ),
+    'psr/simple-cache' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
+    ),
+    'psr/simple-cache-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'qiniu/php-sdk' => 
+    array (
+      'pretty_version' => 'v7.4.0',
+      'version' => '7.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '1c6bc89166e524a40ee42bf516fb99ffc6401c82',
+    ),
+    'ralouphie/getallheaders' => 
+    array (
+      'pretty_version' => '3.0.3',
+      'version' => '3.0.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '120b605dfeb996808c31b6477290a714d356e822',
+    ),
+    'stechstudio/backoff' => 
+    array (
+      'pretty_version' => '1.2',
+      'version' => '1.2.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '816e46107a6be2e1072ba0ff2cb26034872dfa49',
+    ),
+    'swoole/ide-helper' => 
+    array (
+      'pretty_version' => '4.7.1',
+      'version' => '4.7.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '918a98b5b264425fdb59461d9bbd7f9b504ead71',
+    ),
+    'symfony/cache' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '864867b13bd67347497ce956f4b253f8fe18b80c',
+    ),
+    'symfony/cache-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'c0446463729b89dd4fa62e9aeecc80287323615d',
+    ),
+    'symfony/cache-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0|2.0',
+      ),
+    ),
+    'symfony/deprecation-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5f38c8804a9e97d23e0c8d63341088cd8a22d627',
+    ),
+    'symfony/event-dispatcher' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'ce7b20d69c66a20939d8952b617506a44d102130',
+    ),
+    'symfony/event-dispatcher-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '69fee1ad2332a7cbab3aca13591953da9cdb7a11',
+    ),
+    'symfony/event-dispatcher-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '2.0',
+      ),
+    ),
+    'symfony/finder' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'a10000ada1e600d109a6c7632e9ac42e8bf2fb93',
+    ),
+    'symfony/http-foundation' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'e36c8e5502b4f3f0190c675f1c1f1248a64f04e5',
+    ),
+    'symfony/polyfill-mbstring' => 
+    array (
+      'pretty_version' => 'v1.23.1',
+      'version' => '1.23.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '9174a3d80210dca8daa7f31fec659150bbeabfc6',
+    ),
+    'symfony/polyfill-php72' => 
+    array (
+      'pretty_version' => 'v1.23.0',
+      'version' => '1.23.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '9a142215a36a3888e30d0a9eeea9766764e96976',
+    ),
+    'symfony/polyfill-php73' => 
+    array (
+      'pretty_version' => 'v1.23.0',
+      'version' => '1.23.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'fba8933c384d6476ab14fb7b8526e5287ca7e010',
+    ),
+    'symfony/polyfill-php80' => 
+    array (
+      'pretty_version' => 'v1.23.1',
+      'version' => '1.23.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '1100343ed1a92e3a38f9ae122fc0eb21602547be',
+    ),
+    'symfony/process' => 
+    array (
+      'pretty_version' => 'v4.4.30',
+      'version' => '4.4.30.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d',
+    ),
+    'symfony/psr-http-message-bridge' => 
+    array (
+      'pretty_version' => 'v2.1.1',
+      'version' => '2.1.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'c9012994c4b4fb23e7c57dd86b763a417a04feba',
+    ),
+    'symfony/service-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb',
+    ),
+    'symfony/translation' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '4d595a6d15fd3a2c67f6f31d14d15d3b7356d7a6',
+    ),
+    'symfony/translation-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '95c812666f3e91db75385749fe219c5e494c7f95',
+    ),
+    'symfony/translation-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '2.3',
+      ),
+    ),
+    'symfony/var-dumper' => 
+    array (
+      'pretty_version' => 'v4.4.30',
+      'version' => '4.4.30.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c',
+    ),
+    'symfony/var-exporter' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '2ded877ab0574d8b646f4eb3f716f8ed7ee7f392',
+    ),
+    'topthink/framework' => 
+    array (
+      'pretty_version' => 'v6.0.9',
+      'version' => '6.0.9.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '0b5fb453f0e533de3af3a1ab6a202510b61be617',
+    ),
+    'topthink/think' => 
+    array (
+      'pretty_version' => 'dev-master',
+      'version' => 'dev-master',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '05fff7683f644fab2c795b060cb71686f72d52fb',
+    ),
+    'topthink/think-helper' => 
+    array (
+      'pretty_version' => 'v3.1.5',
+      'version' => '3.1.5.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905',
+    ),
+    'topthink/think-orm' => 
+    array (
+      'pretty_version' => 'v2.0.44',
+      'version' => '2.0.44.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5d3d5c1ebf8bfccf34bacd90edb42989b16ea409',
+    ),
+    'topthink/think-queue' => 
+    array (
+      'pretty_version' => 'v3.0.6',
+      'version' => '3.0.6.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'a9f81126bdd52d036461e0c6556592dd478c8728',
+    ),
+    'topthink/think-swoole' => 
+    array (
+      'pretty_version' => 'v4.0.5',
+      'version' => '4.0.5.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '8118fc0422c7a57bc69ce2e505739671654667a3',
+    ),
+    'topthink/think-trace' => 
+    array (
+      'pretty_version' => 'v1.4',
+      'version' => '1.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '9a9fa8f767b6c66c5a133ad21ca1bc96ad329444',
+    ),
+    'xaboy/form-builder' => 
+    array (
+      'pretty_version' => '2.0.19',
+      'version' => '2.0.19.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5cf3f5435a63954c1d11ba82c3cd4cfe3d145acf',
+    ),
+  ),
+);
+private static $canGetVendors;
+private static $installedByVendor = array();
+
+
+
+
+
+
+
+public static function getInstalledPackages()
+{
+$packages = array();
+foreach (self::getInstalled() as $installed) {
+$packages[] = array_keys($installed['versions']);
+}
+
+if (1 === \count($packages)) {
+return $packages[0];
+}
+
+return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+}
+
+
+
+
+
+
+
+
+
+public static function isInstalled($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (isset($installed['versions'][$packageName])) {
+return true;
+}
+}
+
+return false;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+public static function satisfies(VersionParser $parser, $packageName, $constraint)
+{
+$constraint = $parser->parseConstraints($constraint);
+$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+return $provided->matches($constraint);
+}
+
+
+
+
+
+
+
+
+
+
+public static function getVersionRanges($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (!isset($installed['versions'][$packageName])) {
+continue;
+}
+
+$ranges = array();
+if (isset($installed['versions'][$packageName]['pretty_version'])) {
+$ranges[] = $installed['versions'][$packageName]['pretty_version'];
+}
+if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+}
+if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+}
+if (array_key_exists('provided', $installed['versions'][$packageName])) {
+$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+}
+
+return implode(' || ', $ranges);
+}
+
+throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+}
+
+
+
+
+
+public static function getVersion($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (!isset($installed['versions'][$packageName])) {
+continue;
+}
+
+if (!isset($installed['versions'][$packageName]['version'])) {
+return null;
+}
+
+return $installed['versions'][$packageName]['version'];
+}
+
+throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+}
+
+
+
+
+
+public static function getPrettyVersion($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (!isset($installed['versions'][$packageName])) {
+continue;
+}
+
+if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+return null;
+}
+
+return $installed['versions'][$packageName]['pretty_version'];
+}
+
+throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+}
+
+
+
+
+
+public static function getReference($packageName)
+{
+foreach (self::getInstalled() as $installed) {
+if (!isset($installed['versions'][$packageName])) {
+continue;
+}
+
+if (!isset($installed['versions'][$packageName]['reference'])) {
+return null;
+}
+
+return $installed['versions'][$packageName]['reference'];
+}
+
+throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+}
+
+
+
+
+
+public static function getRootPackage()
+{
+$installed = self::getInstalled();
+
+return $installed[0]['root'];
+}
+
+
+
+
+
+
+
+
+public static function getRawData()
+{
+@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+return self::$installed;
+}
+
+
+
+
+
+
+
+public static function getAllRawData()
+{
+return self::getInstalled();
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+public static function reload($data)
+{
+self::$installed = $data;
+self::$installedByVendor = array();
+}
+
+
+
+
+
+private static function getInstalled()
+{
+if (null === self::$canGetVendors) {
+self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+}
+
+$installed = array();
+
+if (self::$canGetVendors) {
+foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+if (isset(self::$installedByVendor[$vendorDir])) {
+$installed[] = self::$installedByVendor[$vendorDir];
+} elseif (is_file($vendorDir.'/composer/installed.php')) {
+$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+}
+}
+}
+
+$installed[] = self::$installed;
+
+return $installed;
+}
 }

+ 68 - 0
vendor/composer/autoload_classmap.php

@@ -9,6 +9,74 @@ return array(
     'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
     'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
     'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
+    'Nette\\ArgumentOutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\DeprecatedException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\DirectoryNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\FileNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\HtmlStringable' => $vendorDir . '/nette/utils/src/HtmlStringable.php',
+    'Nette\\IOException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\InvalidArgumentException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\InvalidStateException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\Iterators\\CachingIterator' => $vendorDir . '/nette/utils/src/Iterators/CachingIterator.php',
+    'Nette\\Iterators\\Mapper' => $vendorDir . '/nette/utils/src/Iterators/Mapper.php',
+    'Nette\\Localization\\ITranslator' => $vendorDir . '/nette/utils/src/compatibility.php',
+    'Nette\\Localization\\Translator' => $vendorDir . '/nette/utils/src/Translator.php',
+    'Nette\\MemberAccessException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\NotImplementedException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\NotSupportedException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\OutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\PhpGenerator\\Attribute' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Attribute.php',
+    'Nette\\PhpGenerator\\ClassType' => $vendorDir . '/nette/php-generator/src/PhpGenerator/ClassType.php',
+    'Nette\\PhpGenerator\\Closure' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Closure.php',
+    'Nette\\PhpGenerator\\Constant' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Constant.php',
+    'Nette\\PhpGenerator\\Dumper' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Dumper.php',
+    'Nette\\PhpGenerator\\EnumCase' => $vendorDir . '/nette/php-generator/src/PhpGenerator/EnumCase.php',
+    'Nette\\PhpGenerator\\Factory' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Factory.php',
+    'Nette\\PhpGenerator\\GlobalFunction' => $vendorDir . '/nette/php-generator/src/PhpGenerator/GlobalFunction.php',
+    'Nette\\PhpGenerator\\Helpers' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Helpers.php',
+    'Nette\\PhpGenerator\\Literal' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Literal.php',
+    'Nette\\PhpGenerator\\Method' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Method.php',
+    'Nette\\PhpGenerator\\Parameter' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Parameter.php',
+    'Nette\\PhpGenerator\\PhpFile' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpFile.php',
+    'Nette\\PhpGenerator\\PhpLiteral' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpLiteral.php',
+    'Nette\\PhpGenerator\\PhpNamespace' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpNamespace.php',
+    'Nette\\PhpGenerator\\Printer' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Printer.php',
+    'Nette\\PhpGenerator\\PromotedParameter' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PromotedParameter.php',
+    'Nette\\PhpGenerator\\Property' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Property.php',
+    'Nette\\PhpGenerator\\PsrPrinter' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PsrPrinter.php',
+    'Nette\\PhpGenerator\\Traits\\AttributeAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/AttributeAware.php',
+    'Nette\\PhpGenerator\\Traits\\CommentAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php',
+    'Nette\\PhpGenerator\\Traits\\FunctionLike' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php',
+    'Nette\\PhpGenerator\\Traits\\NameAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/NameAware.php',
+    'Nette\\PhpGenerator\\Traits\\VisibilityAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php',
+    'Nette\\PhpGenerator\\Type' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Type.php',
+    'Nette\\SmartObject' => $vendorDir . '/nette/utils/src/SmartObject.php',
+    'Nette\\StaticClass' => $vendorDir . '/nette/utils/src/StaticClass.php',
+    'Nette\\UnexpectedValueException' => $vendorDir . '/nette/utils/src/exceptions.php',
+    'Nette\\Utils\\ArrayHash' => $vendorDir . '/nette/utils/src/Utils/ArrayHash.php',
+    'Nette\\Utils\\ArrayList' => $vendorDir . '/nette/utils/src/Utils/ArrayList.php',
+    'Nette\\Utils\\Arrays' => $vendorDir . '/nette/utils/src/Utils/Arrays.php',
+    'Nette\\Utils\\AssertionException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+    'Nette\\Utils\\Callback' => $vendorDir . '/nette/utils/src/Utils/Callback.php',
+    'Nette\\Utils\\DateTime' => $vendorDir . '/nette/utils/src/Utils/DateTime.php',
+    'Nette\\Utils\\FileSystem' => $vendorDir . '/nette/utils/src/Utils/FileSystem.php',
+    'Nette\\Utils\\Floats' => $vendorDir . '/nette/utils/src/Utils/Floats.php',
+    'Nette\\Utils\\Helpers' => $vendorDir . '/nette/utils/src/Utils/Helpers.php',
+    'Nette\\Utils\\Html' => $vendorDir . '/nette/utils/src/Utils/Html.php',
+    'Nette\\Utils\\IHtmlString' => $vendorDir . '/nette/utils/src/compatibility.php',
+    'Nette\\Utils\\Image' => $vendorDir . '/nette/utils/src/Utils/Image.php',
+    'Nette\\Utils\\ImageException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+    'Nette\\Utils\\Json' => $vendorDir . '/nette/utils/src/Utils/Json.php',
+    'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+    'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php',
+    'Nette\\Utils\\ObjectMixin' => $vendorDir . '/nette/utils/src/Utils/ObjectMixin.php',
+    'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php',
+    'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php',
+    'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php',
+    'Nette\\Utils\\RegexpException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+    'Nette\\Utils\\Strings' => $vendorDir . '/nette/utils/src/Utils/Strings.php',
+    'Nette\\Utils\\UnknownImageFileException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
+    'Nette\\Utils\\Validators' => $vendorDir . '/nette/utils/src/Utils/Validators.php',
     'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
     'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
     'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',

+ 3 - 1
vendor/composer/autoload_files.php

@@ -18,8 +18,10 @@ return array(
     '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
     '35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php',
     'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
+    'cc56288302d9df745d97c934d6a6e5f0' => $vendorDir . '/topthink/think-queue/src/common.php',
+    '9a31621970d4295d5f00acf4e277e6f9' => $vendorDir . '/stechstudio/backoff/src/helpers.php',
     'f0e7e63bbb278a92db02393536748c5f' => $vendorDir . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
     '6747f579ad6817f318cc3a7e7a0abb93' => $vendorDir . '/overtrue/wechat/src/Kernel/Helpers.php',
     '841780ea2e1d6545ea3a253239d59c05' => $vendorDir . '/qiniu/php-sdk/src/Qiniu/functions.php',
-    'cc56288302d9df745d97c934d6a6e5f0' => $vendorDir . '/topthink/think-queue/src/common.php',
+    'af46dcea2921209ac30627b964175f13' => $vendorDir . '/topthink/think-swoole/src/helpers.php',
 );

+ 5 - 0
vendor/composer/autoload_psr4.php

@@ -7,7 +7,9 @@ $baseDir = dirname($vendorDir);
 
 return array(
     'think\\trace\\' => array($vendorDir . '/topthink/think-trace/src'),
+    'think\\swoole\\' => array($vendorDir . '/topthink/think-swoole/src'),
     'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-queue/src'),
+    'ln\\' => array($baseDir . '/ln'),
     'app\\' => array($baseDir . '/app'),
     'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
     'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
@@ -22,9 +24,12 @@ return array(
     'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
     'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
     'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
+    'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
     'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
     'Symfony\\Component\\Cache\\' => array($vendorDir . '/symfony/cache'),
     'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'),
+    'Smf\\ConnectionPool\\' => array($vendorDir . '/open-smf/connection-pool/src'),
+    'STS\\Backoff\\' => array($vendorDir . '/stechstudio/backoff/src'),
     'Qiniu\\' => array($vendorDir . '/qiniu/php-sdk/src/Qiniu'),
     'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
     'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),

+ 99 - 1
vendor/composer/autoload_static.php

@@ -19,18 +19,25 @@ class ComposerStaticInit8a48fc5a32e8faaa2f5d0dcc3423f796
         '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
         '35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php',
         'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
+        'cc56288302d9df745d97c934d6a6e5f0' => __DIR__ . '/..' . '/topthink/think-queue/src/common.php',
+        '9a31621970d4295d5f00acf4e277e6f9' => __DIR__ . '/..' . '/stechstudio/backoff/src/helpers.php',
         'f0e7e63bbb278a92db02393536748c5f' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Support/Helpers.php',
         '6747f579ad6817f318cc3a7e7a0abb93' => __DIR__ . '/..' . '/overtrue/wechat/src/Kernel/Helpers.php',
         '841780ea2e1d6545ea3a253239d59c05' => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu/functions.php',
-        'cc56288302d9df745d97c934d6a6e5f0' => __DIR__ . '/..' . '/topthink/think-queue/src/common.php',
+        'af46dcea2921209ac30627b964175f13' => __DIR__ . '/..' . '/topthink/think-swoole/src/helpers.php',
     );
 
     public static $prefixLengthsPsr4 = array (
         't' => 
         array (
             'think\\trace\\' => 12,
+            'think\\swoole\\' => 13,
             'think\\' => 6,
         ),
+        'l' => 
+        array (
+            'ln\\' => 3,
+        ),
         'a' => 
         array (
             'app\\' => 4,
@@ -50,9 +57,12 @@ class ComposerStaticInit8a48fc5a32e8faaa2f5d0dcc3423f796
             'Symfony\\Component\\Translation\\' => 30,
             'Symfony\\Component\\Process\\' => 26,
             'Symfony\\Component\\HttpFoundation\\' => 33,
+            'Symfony\\Component\\Finder\\' => 25,
             'Symfony\\Component\\EventDispatcher\\' => 34,
             'Symfony\\Component\\Cache\\' => 24,
             'Symfony\\Bridge\\PsrHttpMessage\\' => 30,
+            'Smf\\ConnectionPool\\' => 19,
+            'STS\\Backoff\\' => 12,
         ),
         'Q' => 
         array (
@@ -121,6 +131,10 @@ class ComposerStaticInit8a48fc5a32e8faaa2f5d0dcc3423f796
         array (
             0 => __DIR__ . '/..' . '/topthink/think-trace/src',
         ),
+        'think\\swoole\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/topthink/think-swoole/src',
+        ),
         'think\\' => 
         array (
             0 => __DIR__ . '/..' . '/topthink/framework/src/think',
@@ -128,6 +142,10 @@ class ComposerStaticInit8a48fc5a32e8faaa2f5d0dcc3423f796
             2 => __DIR__ . '/..' . '/topthink/think-orm/src',
             3 => __DIR__ . '/..' . '/topthink/think-queue/src',
         ),
+        'ln\\' => 
+        array (
+            0 => __DIR__ . '/../..' . '/ln',
+        ),
         'app\\' => 
         array (
             0 => __DIR__ . '/../..' . '/app',
@@ -184,6 +202,10 @@ class ComposerStaticInit8a48fc5a32e8faaa2f5d0dcc3423f796
         array (
             0 => __DIR__ . '/..' . '/symfony/http-foundation',
         ),
+        'Symfony\\Component\\Finder\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/finder',
+        ),
         'Symfony\\Component\\EventDispatcher\\' => 
         array (
             0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
@@ -196,6 +218,14 @@ class ComposerStaticInit8a48fc5a32e8faaa2f5d0dcc3423f796
         array (
             0 => __DIR__ . '/..' . '/symfony/psr-http-message-bridge',
         ),
+        'Smf\\ConnectionPool\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/open-smf/connection-pool/src',
+        ),
+        'STS\\Backoff\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/stechstudio/backoff/src',
+        ),
         'Qiniu\\' => 
         array (
             0 => __DIR__ . '/..' . '/qiniu/php-sdk/src/Qiniu',
@@ -329,6 +359,74 @@ class ComposerStaticInit8a48fc5a32e8faaa2f5d0dcc3423f796
         'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
         'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
         'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
+        'Nette\\ArgumentOutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\DeprecatedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\DirectoryNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\FileNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\HtmlStringable' => __DIR__ . '/..' . '/nette/utils/src/HtmlStringable.php',
+        'Nette\\IOException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\InvalidArgumentException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\InvalidStateException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\Iterators\\CachingIterator' => __DIR__ . '/..' . '/nette/utils/src/Iterators/CachingIterator.php',
+        'Nette\\Iterators\\Mapper' => __DIR__ . '/..' . '/nette/utils/src/Iterators/Mapper.php',
+        'Nette\\Localization\\ITranslator' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php',
+        'Nette\\Localization\\Translator' => __DIR__ . '/..' . '/nette/utils/src/Translator.php',
+        'Nette\\MemberAccessException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\NotImplementedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\NotSupportedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\OutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\PhpGenerator\\Attribute' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Attribute.php',
+        'Nette\\PhpGenerator\\ClassType' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/ClassType.php',
+        'Nette\\PhpGenerator\\Closure' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Closure.php',
+        'Nette\\PhpGenerator\\Constant' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Constant.php',
+        'Nette\\PhpGenerator\\Dumper' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Dumper.php',
+        'Nette\\PhpGenerator\\EnumCase' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/EnumCase.php',
+        'Nette\\PhpGenerator\\Factory' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Factory.php',
+        'Nette\\PhpGenerator\\GlobalFunction' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/GlobalFunction.php',
+        'Nette\\PhpGenerator\\Helpers' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Helpers.php',
+        'Nette\\PhpGenerator\\Literal' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Literal.php',
+        'Nette\\PhpGenerator\\Method' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Method.php',
+        'Nette\\PhpGenerator\\Parameter' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Parameter.php',
+        'Nette\\PhpGenerator\\PhpFile' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpFile.php',
+        'Nette\\PhpGenerator\\PhpLiteral' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpLiteral.php',
+        'Nette\\PhpGenerator\\PhpNamespace' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpNamespace.php',
+        'Nette\\PhpGenerator\\Printer' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Printer.php',
+        'Nette\\PhpGenerator\\PromotedParameter' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PromotedParameter.php',
+        'Nette\\PhpGenerator\\Property' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Property.php',
+        'Nette\\PhpGenerator\\PsrPrinter' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PsrPrinter.php',
+        'Nette\\PhpGenerator\\Traits\\AttributeAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/AttributeAware.php',
+        'Nette\\PhpGenerator\\Traits\\CommentAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php',
+        'Nette\\PhpGenerator\\Traits\\FunctionLike' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php',
+        'Nette\\PhpGenerator\\Traits\\NameAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/NameAware.php',
+        'Nette\\PhpGenerator\\Traits\\VisibilityAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php',
+        'Nette\\PhpGenerator\\Type' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Type.php',
+        'Nette\\SmartObject' => __DIR__ . '/..' . '/nette/utils/src/SmartObject.php',
+        'Nette\\StaticClass' => __DIR__ . '/..' . '/nette/utils/src/StaticClass.php',
+        'Nette\\UnexpectedValueException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php',
+        'Nette\\Utils\\ArrayHash' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayHash.php',
+        'Nette\\Utils\\ArrayList' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayList.php',
+        'Nette\\Utils\\Arrays' => __DIR__ . '/..' . '/nette/utils/src/Utils/Arrays.php',
+        'Nette\\Utils\\AssertionException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+        'Nette\\Utils\\Callback' => __DIR__ . '/..' . '/nette/utils/src/Utils/Callback.php',
+        'Nette\\Utils\\DateTime' => __DIR__ . '/..' . '/nette/utils/src/Utils/DateTime.php',
+        'Nette\\Utils\\FileSystem' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileSystem.php',
+        'Nette\\Utils\\Floats' => __DIR__ . '/..' . '/nette/utils/src/Utils/Floats.php',
+        'Nette\\Utils\\Helpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/Helpers.php',
+        'Nette\\Utils\\Html' => __DIR__ . '/..' . '/nette/utils/src/Utils/Html.php',
+        'Nette\\Utils\\IHtmlString' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php',
+        'Nette\\Utils\\Image' => __DIR__ . '/..' . '/nette/utils/src/Utils/Image.php',
+        'Nette\\Utils\\ImageException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+        'Nette\\Utils\\Json' => __DIR__ . '/..' . '/nette/utils/src/Utils/Json.php',
+        'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+        'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php',
+        'Nette\\Utils\\ObjectMixin' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectMixin.php',
+        'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php',
+        'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php',
+        'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php',
+        'Nette\\Utils\\RegexpException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+        'Nette\\Utils\\Strings' => __DIR__ . '/..' . '/nette/utils/src/Utils/Strings.php',
+        'Nette\\Utils\\UnknownImageFileException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
+        'Nette\\Utils\\Validators' => __DIR__ . '/..' . '/nette/utils/src/Utils/Validators.php',
         'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
         'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
         'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',

+ 448 - 0
vendor/composer/installed.json

@@ -1247,6 +1247,222 @@
             ],
             "install-path": "../nesbot/carbon"
         },
+        {
+            "name": "nette/php-generator",
+            "version": "v3.6.0",
+            "version_normalized": "3.6.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/php-generator.git",
+                "reference": "2f28a34203ea2e730371d913fc06ae45b6e9baaa"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/php-generator/zipball/2f28a34203ea2e730371d913fc06ae45b6e9baaa",
+                "reference": "2f28a34203ea2e730371d913fc06ae45b6e9baaa",
+                "shasum": ""
+            },
+            "require": {
+                "nette/utils": "^3.1.2",
+                "php": ">=7.2 <8.2"
+            },
+            "require-dev": {
+                "nette/tester": "^2.0",
+                "nikic/php-parser": "^4.4",
+                "phpstan/phpstan": "^0.12",
+                "tracy/tracy": "^2.3"
+            },
+            "suggest": {
+                "nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()"
+            },
+            "time": "2021-08-29T15:43:46+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.6-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "code",
+                "nette",
+                "php",
+                "scaffolding"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/php-generator/issues",
+                "source": "https://github.com/nette/php-generator/tree/v3.6.0"
+            },
+            "install-path": "../nette/php-generator"
+        },
+        {
+            "name": "nette/utils",
+            "version": "v3.2.3",
+            "version_normalized": "3.2.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nette/utils.git",
+                "reference": "5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nette/utils/zipball/5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822",
+                "reference": "5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2 <8.1"
+            },
+            "conflict": {
+                "nette/di": "<3.0.6"
+            },
+            "require-dev": {
+                "nette/tester": "~2.0",
+                "phpstan/phpstan": "^0.12",
+                "tracy/tracy": "^2.3"
+            },
+            "suggest": {
+                "ext-gd": "to use Image",
+                "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+                "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"
+            },
+            "time": "2021-08-16T21:05:00+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.2-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause",
+                "GPL-2.0-only",
+                "GPL-3.0-only"
+            ],
+            "authors": [
+                {
+                    "name": "David Grudl",
+                    "homepage": "https://davidgrudl.com"
+                },
+                {
+                    "name": "Nette Community",
+                    "homepage": "https://nette.org/contributors"
+                }
+            ],
+            "description": "🛠  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+            "homepage": "https://nette.org",
+            "keywords": [
+                "array",
+                "core",
+                "datetime",
+                "images",
+                "json",
+                "nette",
+                "paginator",
+                "password",
+                "slugify",
+                "string",
+                "unicode",
+                "utf-8",
+                "utility",
+                "validation"
+            ],
+            "support": {
+                "issues": "https://github.com/nette/utils/issues",
+                "source": "https://github.com/nette/utils/tree/v3.2.3"
+            },
+            "install-path": "../nette/utils"
+        },
+        {
+            "name": "open-smf/connection-pool",
+            "version": "v1.0.16",
+            "version_normalized": "1.0.16.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/open-smf/connection-pool.git",
+                "reference": "f70e47dbf56f1869d3207e15825cf38810b865e0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/open-smf/connection-pool/zipball/f70e47dbf56f1869d3207e15825cf38810b865e0",
+                "reference": "f70e47dbf56f1869d3207e15825cf38810b865e0",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-swoole": ">=4.2.9",
+                "php": ">=7.0.0"
+            },
+            "require-dev": {
+                "swoole/ide-helper": "@dev"
+            },
+            "suggest": {
+                "ext-redis": "A PHP extension for Redis."
+            },
+            "time": "2021-03-01T04:13:24+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Smf\\ConnectionPool\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Xie Biao",
+                    "email": "hhxsv5@sina.com"
+                }
+            ],
+            "description": "A common connection pool based on Swoole is usually used as the database connection pool.",
+            "homepage": "https://github.com/open-smf/connection-pool",
+            "keywords": [
+                "connection-pool",
+                "database-connection-pool",
+                "swoole"
+            ],
+            "support": {
+                "issues": "https://github.com/open-smf/connection-pool/issues",
+                "source": "https://github.com/open-smf/connection-pool"
+            },
+            "install-path": "../open-smf/connection-pool"
+        },
         {
             "name": "overtrue/socialite",
             "version": "3.2.4",
@@ -2160,6 +2376,103 @@
             },
             "install-path": "../ralouphie/getallheaders"
         },
+        {
+            "name": "stechstudio/backoff",
+            "version": "1.2",
+            "version_normalized": "1.2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/stechstudio/backoff.git",
+                "reference": "816e46107a6be2e1072ba0ff2cb26034872dfa49"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/stechstudio/backoff/zipball/816e46107a6be2e1072ba0ff2cb26034872dfa49",
+                "reference": "816e46107a6be2e1072ba0ff2cb26034872dfa49",
+                "shasum": ""
+            },
+            "require-dev": {
+                "phpunit/phpunit": "5.5.*"
+            },
+            "time": "2020-12-26T14:57:10+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "STS\\Backoff\\": "src"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Joseph Szobody",
+                    "email": "joseph@stechstudio.com"
+                }
+            ],
+            "description": "PHP library providing retry functionality with multiple backoff strategies and jitter support",
+            "support": {
+                "issues": "https://github.com/stechstudio/backoff/issues",
+                "source": "https://github.com/stechstudio/backoff/tree/1.2"
+            },
+            "install-path": "../stechstudio/backoff"
+        },
+        {
+            "name": "swoole/ide-helper",
+            "version": "4.7.1",
+            "version_normalized": "4.7.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/swoole/ide-helper.git",
+                "reference": "918a98b5b264425fdb59461d9bbd7f9b504ead71"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/swoole/ide-helper/zipball/918a98b5b264425fdb59461d9bbd7f9b504ead71",
+                "reference": "918a98b5b264425fdb59461d9bbd7f9b504ead71",
+                "shasum": ""
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "~6.5.0",
+                "laminas/laminas-code": "~3.4.0",
+                "squizlabs/php_codesniffer": "~3.5.0",
+                "symfony/filesystem": "~4.0"
+            },
+            "time": "2021-08-19T17:25:57+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Team Swoole",
+                    "email": "team@swoole.com"
+                }
+            ],
+            "description": "IDE help files for Swoole.",
+            "support": {
+                "issues": "https://github.com/swoole/ide-helper/issues",
+                "source": "https://github.com/swoole/ide-helper/tree/4.7.1"
+            },
+            "funding": [
+                {
+                    "url": "https://gitee.com/swoole/swoole?donate=true",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/swoole",
+                    "type": "github"
+                }
+            ],
+            "install-path": "../swoole/ide-helper"
+        },
         {
             "name": "symfony/cache",
             "version": "v5.3.7",
@@ -2612,6 +2925,71 @@
             ],
             "install-path": "../symfony/event-dispatcher-contracts"
         },
+        {
+            "name": "symfony/finder",
+            "version": "v5.3.7",
+            "version_normalized": "5.3.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/a10000ada1e600d109a6c7632e9ac42e8bf2fb93",
+                "reference": "a10000ada1e600d109a6c7632e9ac42e8bf2fb93",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-php80": "^1.16"
+            },
+            "time": "2021-08-04T21:20:46+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Finds files and directories via an intuitive fluent interface",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/finder/tree/v5.3.7"
+            },
+            "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"
+                }
+            ],
+            "install-path": "../symfony/finder"
+        },
         {
             "name": "symfony/http-foundation",
             "version": "v5.3.7",
@@ -3935,6 +4313,76 @@
             },
             "install-path": "../topthink/think-queue"
         },
+        {
+            "name": "topthink/think-swoole",
+            "version": "v4.0.5",
+            "version_normalized": "4.0.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/top-think/think-swoole.git",
+                "reference": "8118fc0422c7a57bc69ce2e505739671654667a3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/top-think/think-swoole/zipball/8118fc0422c7a57bc69ce2e505739671654667a3",
+                "reference": "8118fc0422c7a57bc69ce2e505739671654667a3",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*",
+                "ext-swoole": ">=4.6",
+                "nette/php-generator": "^3.2",
+                "open-smf/connection-pool": "~1.0",
+                "php": ">=7.4",
+                "stechstudio/backoff": "^1.2",
+                "swoole/ide-helper": "^4.3",
+                "symfony/finder": "^4.3.2|^5.1",
+                "topthink/framework": "^6.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^9.5",
+                "symfony/var-dumper": "^4.3|^5.1",
+                "topthink/think-queue": "^3.0",
+                "topthink/think-tracing": "^1.0"
+            },
+            "time": "2021-09-03T05:40:24+00:00",
+            "type": "library",
+            "extra": {
+                "think": {
+                    "services": [
+                        "think\\swoole\\Service"
+                    ],
+                    "config": {
+                        "swoole": "src/config/swoole.php"
+                    }
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "think\\swoole\\": "src"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "liu21st",
+                    "email": "liu21st@gmail.com"
+                }
+            ],
+            "description": "Swoole extend for thinkphp",
+            "support": {
+                "issues": "https://github.com/top-think/think-swoole/issues",
+                "source": "https://github.com/top-think/think-swoole/tree/v4.0.5"
+            },
+            "install-path": "../topthink/think-swoole"
+        },
         {
             "name": "topthink/think-trace",
             "version": "v1.4",

+ 632 - 558
vendor/composer/installed.php

@@ -1,560 +1,634 @@
-<?php return array(
-    'root' => array(
-        'pretty_version' => '1.0.0+no-version-set',
-        'version' => '1.0.0.0',
-        'type' => 'project',
-        'install_path' => __DIR__ . '/../../',
-        'aliases' => array(),
-        'reference' => NULL,
-        'name' => 'topthink/think',
-        'dev' => true,
-    ),
-    'versions' => array(
-        'aliyuncs/oss-sdk-php' => array(
-            'pretty_version' => 'v2.4.3',
-            'version' => '2.4.3.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../aliyuncs/oss-sdk-php',
-            'aliases' => array(),
-            'reference' => '4ccead614915ee6685bf30016afb01aabd347e46',
-            'dev_requirement' => false,
-        ),
-        'bacon/bacon-qr-code' => array(
-            'pretty_version' => '2.0.4',
-            'version' => '2.0.4.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../bacon/bacon-qr-code',
-            'aliases' => array(),
-            'reference' => 'f73543ac4e1def05f1a70bcd1525c8a157a1ad09',
-            'dev_requirement' => false,
-        ),
-        'dasprid/enum' => array(
-            'pretty_version' => '1.0.3',
-            'version' => '1.0.3.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../dasprid/enum',
-            'aliases' => array(),
-            'reference' => '5abf82f213618696dda8e3bf6f64dd042d8542b2',
-            'dev_requirement' => false,
-        ),
-        'doctrine/annotations' => array(
-            'pretty_version' => '1.13.2',
-            'version' => '1.13.2.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../doctrine/annotations',
-            'aliases' => array(),
-            'reference' => '5b668aef16090008790395c02c893b1ba13f7e08',
-            'dev_requirement' => false,
-        ),
-        'doctrine/lexer' => array(
-            'pretty_version' => '1.2.1',
-            'version' => '1.2.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../doctrine/lexer',
-            'aliases' => array(),
-            'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042',
-            'dev_requirement' => false,
-        ),
-        'easywechat-composer/easywechat-composer' => array(
-            'pretty_version' => '1.4.1',
-            'version' => '1.4.1.0',
-            'type' => 'composer-plugin',
-            'install_path' => __DIR__ . '/../easywechat-composer/easywechat-composer',
-            'aliases' => array(),
-            'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd',
-            'dev_requirement' => false,
-        ),
-        'endroid/qr-code' => array(
-            'pretty_version' => '4.3.0',
-            'version' => '4.3.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../endroid/qr-code',
-            'aliases' => array(),
-            'reference' => '98f6d4024289ad3a8d7f3e63cab947ef6929dcdb',
-            'dev_requirement' => false,
-        ),
-        'firebase/php-jwt' => array(
-            'pretty_version' => 'v5.4.0',
-            'version' => '5.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../firebase/php-jwt',
-            'aliases' => array(),
-            'reference' => 'd2113d9b2e0e349796e72d2a63cf9319100382d2',
-            'dev_requirement' => false,
-        ),
-        'guzzlehttp/guzzle' => array(
-            'pretty_version' => '7.3.0',
-            'version' => '7.3.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
-            'aliases' => array(),
-            'reference' => '7008573787b430c1c1f650e3722d9bba59967628',
-            'dev_requirement' => false,
-        ),
-        'guzzlehttp/promises' => array(
-            'pretty_version' => '1.4.1',
-            'version' => '1.4.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../guzzlehttp/promises',
-            'aliases' => array(),
-            'reference' => '8e7d04f1f6450fef59366c399cfad4b9383aa30d',
-            'dev_requirement' => false,
-        ),
-        'guzzlehttp/psr7' => array(
-            'pretty_version' => '2.0.0',
-            'version' => '2.0.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../guzzlehttp/psr7',
-            'aliases' => array(),
-            'reference' => '1dc8d9cba3897165e16d12bb13d813afb1eb3fe7',
-            'dev_requirement' => false,
-        ),
-        'league/flysystem' => array(
-            'pretty_version' => '1.1.5',
-            'version' => '1.1.5.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../league/flysystem',
-            'aliases' => array(),
-            'reference' => '18634df356bfd4119fe3d6156bdb990c414c14ea',
-            'dev_requirement' => false,
-        ),
-        'league/flysystem-cached-adapter' => array(
-            'pretty_version' => '1.1.0',
-            'version' => '1.1.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../league/flysystem-cached-adapter',
-            'aliases' => array(),
-            'reference' => 'd1925efb2207ac4be3ad0c40b8277175f99ffaff',
-            'dev_requirement' => false,
-        ),
-        'league/mime-type-detection' => array(
-            'pretty_version' => '1.7.0',
-            'version' => '1.7.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../league/mime-type-detection',
-            'aliases' => array(),
-            'reference' => '3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3',
-            'dev_requirement' => false,
-        ),
-        'monolog/monolog' => array(
-            'pretty_version' => '2.3.2',
-            'version' => '2.3.2.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../monolog/monolog',
-            'aliases' => array(),
-            'reference' => '71312564759a7db5b789296369c1a264efc43aad',
-            'dev_requirement' => false,
-        ),
-        'nesbot/carbon' => array(
-            'pretty_version' => '2.53.1',
-            'version' => '2.53.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../nesbot/carbon',
-            'aliases' => array(),
-            'reference' => 'f4655858a784988f880c1b8c7feabbf02dfdf045',
-            'dev_requirement' => false,
-        ),
-        'overtrue/socialite' => array(
-            'pretty_version' => '3.2.4',
-            'version' => '3.2.4.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../overtrue/socialite',
-            'aliases' => array(),
-            'reference' => '7605f4370a92ea4811cc5016d4709ef3d545f51c',
-            'dev_requirement' => false,
-        ),
-        'overtrue/wechat' => array(
-            'pretty_version' => '5.7.2',
-            'version' => '5.7.2.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../overtrue/wechat',
-            'aliases' => array(),
-            'reference' => 'd57d2104fe705511374ae3627b0d9b7a761d87b9',
-            'dev_requirement' => false,
-        ),
-        'phpoffice/phpexcel' => array(
-            'pretty_version' => '1.8.1',
-            'version' => '1.8.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../phpoffice/phpexcel',
-            'aliases' => array(),
-            'reference' => '372c7cbb695a6f6f1e62649381aeaa37e7e70b32',
-            'dev_requirement' => false,
-        ),
-        'pimple/pimple' => array(
-            'pretty_version' => 'v3.4.0',
-            'version' => '3.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../pimple/pimple',
-            'aliases' => array(),
-            'reference' => '86406047271859ffc13424a048541f4531f53601',
-            'dev_requirement' => false,
-        ),
-        'psr/cache' => array(
-            'pretty_version' => '1.0.1',
-            'version' => '1.0.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../psr/cache',
-            'aliases' => array(),
-            'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8',
-            'dev_requirement' => false,
-        ),
-        'psr/cache-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '1.0|2.0',
-            ),
-        ),
-        'psr/container' => array(
-            'pretty_version' => '1.1.1',
-            'version' => '1.1.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../psr/container',
-            'aliases' => array(),
-            'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf',
-            'dev_requirement' => false,
-        ),
-        'psr/event-dispatcher' => array(
-            'pretty_version' => '1.0.0',
-            'version' => '1.0.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../psr/event-dispatcher',
-            'aliases' => array(),
-            'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
-            'dev_requirement' => false,
-        ),
-        'psr/event-dispatcher-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '1.0',
-            ),
-        ),
-        'psr/http-client' => array(
-            'pretty_version' => '1.0.1',
-            'version' => '1.0.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../psr/http-client',
-            'aliases' => array(),
-            'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
-            'dev_requirement' => false,
-        ),
-        'psr/http-client-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '1.0',
-            ),
-        ),
-        'psr/http-factory' => array(
-            'pretty_version' => '1.0.1',
-            'version' => '1.0.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../psr/http-factory',
-            'aliases' => array(),
-            'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
-            'dev_requirement' => false,
-        ),
-        'psr/http-factory-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '1.0',
-            ),
-        ),
-        'psr/http-message' => array(
-            'pretty_version' => '1.0.1',
-            'version' => '1.0.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../psr/http-message',
-            'aliases' => array(),
-            'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
-            'dev_requirement' => false,
-        ),
-        'psr/http-message-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '1.0',
-            ),
-        ),
-        'psr/log' => array(
-            'pretty_version' => '1.1.4',
-            'version' => '1.1.4.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../psr/log',
-            'aliases' => array(),
-            'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
-            'dev_requirement' => false,
-        ),
-        'psr/log-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '1.0.0',
-            ),
-        ),
-        'psr/simple-cache' => array(
-            'pretty_version' => '1.0.1',
-            'version' => '1.0.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../psr/simple-cache',
-            'aliases' => array(),
-            'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
-            'dev_requirement' => false,
-        ),
-        'psr/simple-cache-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '1.0',
-            ),
-        ),
-        'qiniu/php-sdk' => array(
-            'pretty_version' => 'v7.4.0',
-            'version' => '7.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../qiniu/php-sdk',
-            'aliases' => array(),
-            'reference' => '1c6bc89166e524a40ee42bf516fb99ffc6401c82',
-            'dev_requirement' => false,
-        ),
-        'ralouphie/getallheaders' => array(
-            'pretty_version' => '3.0.3',
-            'version' => '3.0.3.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../ralouphie/getallheaders',
-            'aliases' => array(),
-            'reference' => '120b605dfeb996808c31b6477290a714d356e822',
-            'dev_requirement' => false,
-        ),
-        'symfony/cache' => array(
-            'pretty_version' => 'v5.3.7',
-            'version' => '5.3.7.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/cache',
-            'aliases' => array(),
-            'reference' => '864867b13bd67347497ce956f4b253f8fe18b80c',
-            'dev_requirement' => false,
-        ),
-        'symfony/cache-contracts' => array(
-            'pretty_version' => 'v2.4.0',
-            'version' => '2.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/cache-contracts',
-            'aliases' => array(),
-            'reference' => 'c0446463729b89dd4fa62e9aeecc80287323615d',
-            'dev_requirement' => false,
-        ),
-        'symfony/cache-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '1.0|2.0',
-            ),
-        ),
-        'symfony/deprecation-contracts' => array(
-            'pretty_version' => 'v2.4.0',
-            'version' => '2.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
-            'aliases' => array(),
-            'reference' => '5f38c8804a9e97d23e0c8d63341088cd8a22d627',
-            'dev_requirement' => false,
-        ),
-        'symfony/event-dispatcher' => array(
-            'pretty_version' => 'v5.3.7',
-            'version' => '5.3.7.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/event-dispatcher',
-            'aliases' => array(),
-            'reference' => 'ce7b20d69c66a20939d8952b617506a44d102130',
-            'dev_requirement' => false,
-        ),
-        'symfony/event-dispatcher-contracts' => array(
-            'pretty_version' => 'v2.4.0',
-            'version' => '2.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts',
-            'aliases' => array(),
-            'reference' => '69fee1ad2332a7cbab3aca13591953da9cdb7a11',
-            'dev_requirement' => false,
-        ),
-        'symfony/event-dispatcher-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '2.0',
-            ),
-        ),
-        'symfony/http-foundation' => array(
-            'pretty_version' => 'v5.3.7',
-            'version' => '5.3.7.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/http-foundation',
-            'aliases' => array(),
-            'reference' => 'e36c8e5502b4f3f0190c675f1c1f1248a64f04e5',
-            'dev_requirement' => false,
-        ),
-        'symfony/polyfill-mbstring' => array(
-            'pretty_version' => 'v1.23.1',
-            'version' => '1.23.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
-            'aliases' => array(),
-            'reference' => '9174a3d80210dca8daa7f31fec659150bbeabfc6',
-            'dev_requirement' => false,
-        ),
-        'symfony/polyfill-php72' => array(
-            'pretty_version' => 'v1.23.0',
-            'version' => '1.23.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/polyfill-php72',
-            'aliases' => array(),
-            'reference' => '9a142215a36a3888e30d0a9eeea9766764e96976',
-            'dev_requirement' => true,
-        ),
-        'symfony/polyfill-php73' => array(
-            'pretty_version' => 'v1.23.0',
-            'version' => '1.23.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/polyfill-php73',
-            'aliases' => array(),
-            'reference' => 'fba8933c384d6476ab14fb7b8526e5287ca7e010',
-            'dev_requirement' => false,
-        ),
-        'symfony/polyfill-php80' => array(
-            'pretty_version' => 'v1.23.1',
-            'version' => '1.23.1.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/polyfill-php80',
-            'aliases' => array(),
-            'reference' => '1100343ed1a92e3a38f9ae122fc0eb21602547be',
-            'dev_requirement' => false,
-        ),
-        'symfony/process' => array(
-            'pretty_version' => 'v4.4.30',
-            'version' => '4.4.30.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/process',
-            'aliases' => array(),
-            'reference' => '13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d',
-            'dev_requirement' => false,
-        ),
-        'symfony/psr-http-message-bridge' => array(
-            'pretty_version' => 'v2.1.1',
-            'version' => '2.1.1.0',
-            'type' => 'symfony-bridge',
-            'install_path' => __DIR__ . '/../symfony/psr-http-message-bridge',
-            'aliases' => array(),
-            'reference' => 'c9012994c4b4fb23e7c57dd86b763a417a04feba',
-            'dev_requirement' => false,
-        ),
-        'symfony/service-contracts' => array(
-            'pretty_version' => 'v2.4.0',
-            'version' => '2.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/service-contracts',
-            'aliases' => array(),
-            'reference' => 'f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb',
-            'dev_requirement' => false,
-        ),
-        'symfony/translation' => array(
-            'pretty_version' => 'v5.3.7',
-            'version' => '5.3.7.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/translation',
-            'aliases' => array(),
-            'reference' => '4d595a6d15fd3a2c67f6f31d14d15d3b7356d7a6',
-            'dev_requirement' => false,
-        ),
-        'symfony/translation-contracts' => array(
-            'pretty_version' => 'v2.4.0',
-            'version' => '2.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/translation-contracts',
-            'aliases' => array(),
-            'reference' => '95c812666f3e91db75385749fe219c5e494c7f95',
-            'dev_requirement' => false,
-        ),
-        'symfony/translation-implementation' => array(
-            'dev_requirement' => false,
-            'provided' => array(
-                0 => '2.3',
-            ),
-        ),
-        'symfony/var-dumper' => array(
-            'pretty_version' => 'v4.4.30',
-            'version' => '4.4.30.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/var-dumper',
-            'aliases' => array(),
-            'reference' => '7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c',
-            'dev_requirement' => true,
-        ),
-        'symfony/var-exporter' => array(
-            'pretty_version' => 'v5.3.7',
-            'version' => '5.3.7.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../symfony/var-exporter',
-            'aliases' => array(),
-            'reference' => '2ded877ab0574d8b646f4eb3f716f8ed7ee7f392',
-            'dev_requirement' => false,
-        ),
-        'topthink/framework' => array(
-            'pretty_version' => 'v6.0.9',
-            'version' => '6.0.9.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../topthink/framework',
-            'aliases' => array(),
-            'reference' => '0b5fb453f0e533de3af3a1ab6a202510b61be617',
-            'dev_requirement' => false,
-        ),
-        'topthink/think' => array(
-            'pretty_version' => '1.0.0+no-version-set',
-            'version' => '1.0.0.0',
-            'type' => 'project',
-            'install_path' => __DIR__ . '/../../',
-            'aliases' => array(),
-            'reference' => NULL,
-            'dev_requirement' => false,
-        ),
-        'topthink/think-helper' => array(
-            'pretty_version' => 'v3.1.5',
-            'version' => '3.1.5.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../topthink/think-helper',
-            'aliases' => array(),
-            'reference' => 'f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905',
-            'dev_requirement' => false,
-        ),
-        'topthink/think-orm' => array(
-            'pretty_version' => 'v2.0.44',
-            'version' => '2.0.44.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../topthink/think-orm',
-            'aliases' => array(),
-            'reference' => '5d3d5c1ebf8bfccf34bacd90edb42989b16ea409',
-            'dev_requirement' => false,
-        ),
-        'topthink/think-queue' => array(
-            'pretty_version' => 'v3.0.6',
-            'version' => '3.0.6.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../topthink/think-queue',
-            'aliases' => array(),
-            'reference' => 'a9f81126bdd52d036461e0c6556592dd478c8728',
-            'dev_requirement' => false,
-        ),
-        'topthink/think-trace' => array(
-            'pretty_version' => 'v1.4',
-            'version' => '1.4.0.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../topthink/think-trace',
-            'aliases' => array(),
-            'reference' => '9a9fa8f767b6c66c5a133ad21ca1bc96ad329444',
-            'dev_requirement' => true,
-        ),
-        'xaboy/form-builder' => array(
-            'pretty_version' => '2.0.19',
-            'version' => '2.0.19.0',
-            'type' => 'library',
-            'install_path' => __DIR__ . '/../xaboy/form-builder',
-            'aliases' => array(),
-            'reference' => '5cf3f5435a63954c1d11ba82c3cd4cfe3d145acf',
-            'dev_requirement' => false,
-        ),
+<?php return array (
+  'root' => 
+  array (
+    'pretty_version' => 'dev-master',
+    'version' => 'dev-master',
+    'aliases' => 
+    array (
     ),
+    'reference' => '05fff7683f644fab2c795b060cb71686f72d52fb',
+    'name' => 'topthink/think',
+  ),
+  'versions' => 
+  array (
+    'aliyuncs/oss-sdk-php' => 
+    array (
+      'pretty_version' => 'v2.4.3',
+      'version' => '2.4.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '4ccead614915ee6685bf30016afb01aabd347e46',
+    ),
+    'bacon/bacon-qr-code' => 
+    array (
+      'pretty_version' => '2.0.4',
+      'version' => '2.0.4.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f73543ac4e1def05f1a70bcd1525c8a157a1ad09',
+    ),
+    'dasprid/enum' => 
+    array (
+      'pretty_version' => '1.0.3',
+      'version' => '1.0.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5abf82f213618696dda8e3bf6f64dd042d8542b2',
+    ),
+    'doctrine/annotations' => 
+    array (
+      'pretty_version' => '1.13.2',
+      'version' => '1.13.2.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5b668aef16090008790395c02c893b1ba13f7e08',
+    ),
+    'doctrine/lexer' => 
+    array (
+      'pretty_version' => '1.2.1',
+      'version' => '1.2.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'e864bbf5904cb8f5bb334f99209b48018522f042',
+    ),
+    'easywechat-composer/easywechat-composer' => 
+    array (
+      'pretty_version' => '1.4.1',
+      'version' => '1.4.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '3fc6a7ab6d3853c0f4e2922539b56cc37ef361cd',
+    ),
+    'endroid/qr-code' => 
+    array (
+      'pretty_version' => '4.3.0',
+      'version' => '4.3.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '98f6d4024289ad3a8d7f3e63cab947ef6929dcdb',
+    ),
+    'firebase/php-jwt' => 
+    array (
+      'pretty_version' => 'v5.4.0',
+      'version' => '5.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd2113d9b2e0e349796e72d2a63cf9319100382d2',
+    ),
+    'guzzlehttp/guzzle' => 
+    array (
+      'pretty_version' => '7.3.0',
+      'version' => '7.3.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '7008573787b430c1c1f650e3722d9bba59967628',
+    ),
+    'guzzlehttp/promises' => 
+    array (
+      'pretty_version' => '1.4.1',
+      'version' => '1.4.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '8e7d04f1f6450fef59366c399cfad4b9383aa30d',
+    ),
+    'guzzlehttp/psr7' => 
+    array (
+      'pretty_version' => '2.0.0',
+      'version' => '2.0.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '1dc8d9cba3897165e16d12bb13d813afb1eb3fe7',
+    ),
+    'league/flysystem' => 
+    array (
+      'pretty_version' => '1.1.5',
+      'version' => '1.1.5.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '18634df356bfd4119fe3d6156bdb990c414c14ea',
+    ),
+    'league/flysystem-cached-adapter' => 
+    array (
+      'pretty_version' => '1.1.0',
+      'version' => '1.1.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd1925efb2207ac4be3ad0c40b8277175f99ffaff',
+    ),
+    'league/mime-type-detection' => 
+    array (
+      'pretty_version' => '1.7.0',
+      'version' => '1.7.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3',
+    ),
+    'monolog/monolog' => 
+    array (
+      'pretty_version' => '2.3.2',
+      'version' => '2.3.2.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '71312564759a7db5b789296369c1a264efc43aad',
+    ),
+    'nesbot/carbon' => 
+    array (
+      'pretty_version' => '2.53.1',
+      'version' => '2.53.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f4655858a784988f880c1b8c7feabbf02dfdf045',
+    ),
+    'nette/php-generator' => 
+    array (
+      'pretty_version' => 'v3.6.0',
+      'version' => '3.6.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '2f28a34203ea2e730371d913fc06ae45b6e9baaa',
+    ),
+    'nette/utils' => 
+    array (
+      'pretty_version' => 'v3.2.3',
+      'version' => '3.2.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5c36cc1ba9bb6abb8a9e425cf054e0c3fd5b9822',
+    ),
+    'open-smf/connection-pool' => 
+    array (
+      'pretty_version' => 'v1.0.16',
+      'version' => '1.0.16.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f70e47dbf56f1869d3207e15825cf38810b865e0',
+    ),
+    'overtrue/socialite' => 
+    array (
+      'pretty_version' => '3.2.4',
+      'version' => '3.2.4.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '7605f4370a92ea4811cc5016d4709ef3d545f51c',
+    ),
+    'overtrue/wechat' => 
+    array (
+      'pretty_version' => '5.7.2',
+      'version' => '5.7.2.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd57d2104fe705511374ae3627b0d9b7a761d87b9',
+    ),
+    'phpoffice/phpexcel' => 
+    array (
+      'pretty_version' => '1.8.1',
+      'version' => '1.8.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '372c7cbb695a6f6f1e62649381aeaa37e7e70b32',
+    ),
+    'pimple/pimple' => 
+    array (
+      'pretty_version' => 'v3.4.0',
+      'version' => '3.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '86406047271859ffc13424a048541f4531f53601',
+    ),
+    'psr/cache' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8',
+    ),
+    'psr/cache-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0|2.0',
+      ),
+    ),
+    'psr/container' => 
+    array (
+      'pretty_version' => '1.1.1',
+      'version' => '1.1.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf',
+    ),
+    'psr/event-dispatcher' => 
+    array (
+      'pretty_version' => '1.0.0',
+      'version' => '1.0.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
+    ),
+    'psr/event-dispatcher-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'psr/http-client' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
+    ),
+    'psr/http-client-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'psr/http-factory' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
+    ),
+    'psr/http-factory-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'psr/http-message' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f6561bf28d520154e4b0ec72be95418abe6d9363',
+    ),
+    'psr/http-message-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'psr/log' => 
+    array (
+      'pretty_version' => '1.1.4',
+      'version' => '1.1.4.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
+    ),
+    'psr/log-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0.0',
+      ),
+    ),
+    'psr/simple-cache' => 
+    array (
+      'pretty_version' => '1.0.1',
+      'version' => '1.0.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b',
+    ),
+    'psr/simple-cache-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0',
+      ),
+    ),
+    'qiniu/php-sdk' => 
+    array (
+      'pretty_version' => 'v7.4.0',
+      'version' => '7.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '1c6bc89166e524a40ee42bf516fb99ffc6401c82',
+    ),
+    'ralouphie/getallheaders' => 
+    array (
+      'pretty_version' => '3.0.3',
+      'version' => '3.0.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '120b605dfeb996808c31b6477290a714d356e822',
+    ),
+    'stechstudio/backoff' => 
+    array (
+      'pretty_version' => '1.2',
+      'version' => '1.2.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '816e46107a6be2e1072ba0ff2cb26034872dfa49',
+    ),
+    'swoole/ide-helper' => 
+    array (
+      'pretty_version' => '4.7.1',
+      'version' => '4.7.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '918a98b5b264425fdb59461d9bbd7f9b504ead71',
+    ),
+    'symfony/cache' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '864867b13bd67347497ce956f4b253f8fe18b80c',
+    ),
+    'symfony/cache-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'c0446463729b89dd4fa62e9aeecc80287323615d',
+    ),
+    'symfony/cache-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '1.0|2.0',
+      ),
+    ),
+    'symfony/deprecation-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5f38c8804a9e97d23e0c8d63341088cd8a22d627',
+    ),
+    'symfony/event-dispatcher' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'ce7b20d69c66a20939d8952b617506a44d102130',
+    ),
+    'symfony/event-dispatcher-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '69fee1ad2332a7cbab3aca13591953da9cdb7a11',
+    ),
+    'symfony/event-dispatcher-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '2.0',
+      ),
+    ),
+    'symfony/finder' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'a10000ada1e600d109a6c7632e9ac42e8bf2fb93',
+    ),
+    'symfony/http-foundation' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'e36c8e5502b4f3f0190c675f1c1f1248a64f04e5',
+    ),
+    'symfony/polyfill-mbstring' => 
+    array (
+      'pretty_version' => 'v1.23.1',
+      'version' => '1.23.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '9174a3d80210dca8daa7f31fec659150bbeabfc6',
+    ),
+    'symfony/polyfill-php72' => 
+    array (
+      'pretty_version' => 'v1.23.0',
+      'version' => '1.23.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '9a142215a36a3888e30d0a9eeea9766764e96976',
+    ),
+    'symfony/polyfill-php73' => 
+    array (
+      'pretty_version' => 'v1.23.0',
+      'version' => '1.23.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'fba8933c384d6476ab14fb7b8526e5287ca7e010',
+    ),
+    'symfony/polyfill-php80' => 
+    array (
+      'pretty_version' => 'v1.23.1',
+      'version' => '1.23.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '1100343ed1a92e3a38f9ae122fc0eb21602547be',
+    ),
+    'symfony/process' => 
+    array (
+      'pretty_version' => 'v4.4.30',
+      'version' => '4.4.30.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '13d3161ef63a8ec21eeccaaf9a4d7f784a87a97d',
+    ),
+    'symfony/psr-http-message-bridge' => 
+    array (
+      'pretty_version' => 'v2.1.1',
+      'version' => '2.1.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'c9012994c4b4fb23e7c57dd86b763a417a04feba',
+    ),
+    'symfony/service-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb',
+    ),
+    'symfony/translation' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '4d595a6d15fd3a2c67f6f31d14d15d3b7356d7a6',
+    ),
+    'symfony/translation-contracts' => 
+    array (
+      'pretty_version' => 'v2.4.0',
+      'version' => '2.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '95c812666f3e91db75385749fe219c5e494c7f95',
+    ),
+    'symfony/translation-implementation' => 
+    array (
+      'provided' => 
+      array (
+        0 => '2.3',
+      ),
+    ),
+    'symfony/var-dumper' => 
+    array (
+      'pretty_version' => 'v4.4.30',
+      'version' => '4.4.30.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '7f65c44c2ce80d3a0fcdb6385ee0ad535e45660c',
+    ),
+    'symfony/var-exporter' => 
+    array (
+      'pretty_version' => 'v5.3.7',
+      'version' => '5.3.7.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '2ded877ab0574d8b646f4eb3f716f8ed7ee7f392',
+    ),
+    'topthink/framework' => 
+    array (
+      'pretty_version' => 'v6.0.9',
+      'version' => '6.0.9.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '0b5fb453f0e533de3af3a1ab6a202510b61be617',
+    ),
+    'topthink/think' => 
+    array (
+      'pretty_version' => 'dev-master',
+      'version' => 'dev-master',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '05fff7683f644fab2c795b060cb71686f72d52fb',
+    ),
+    'topthink/think-helper' => 
+    array (
+      'pretty_version' => 'v3.1.5',
+      'version' => '3.1.5.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905',
+    ),
+    'topthink/think-orm' => 
+    array (
+      'pretty_version' => 'v2.0.44',
+      'version' => '2.0.44.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5d3d5c1ebf8bfccf34bacd90edb42989b16ea409',
+    ),
+    'topthink/think-queue' => 
+    array (
+      'pretty_version' => 'v3.0.6',
+      'version' => '3.0.6.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'a9f81126bdd52d036461e0c6556592dd478c8728',
+    ),
+    'topthink/think-swoole' => 
+    array (
+      'pretty_version' => 'v4.0.5',
+      'version' => '4.0.5.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '8118fc0422c7a57bc69ce2e505739671654667a3',
+    ),
+    'topthink/think-trace' => 
+    array (
+      'pretty_version' => 'v1.4',
+      'version' => '1.4.0.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '9a9fa8f767b6c66c5a133ad21ca1bc96ad329444',
+    ),
+    'xaboy/form-builder' => 
+    array (
+      'pretty_version' => '2.0.19',
+      'version' => '2.0.19.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '5cf3f5435a63954c1d11ba82c3cd4cfe3d145acf',
+    ),
+  ),
 );

+ 43 - 0
vendor/nette/php-generator/composer.json

@@ -0,0 +1,43 @@
+{
+	"name": "nette/php-generator",
+	"description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.1 features.",
+	"keywords": ["nette", "php", "code", "scaffolding"],
+	"homepage": "https://nette.org",
+	"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
+	"authors": [
+		{
+			"name": "David Grudl",
+			"homepage": "https://davidgrudl.com"
+		},
+		{
+			"name": "Nette Community",
+			"homepage": "https://nette.org/contributors"
+		}
+	],
+	"require": {
+		"php": ">=7.2 <8.2",
+		"nette/utils": "^3.1.2"
+	},
+	"require-dev": {
+		"nette/tester": "^2.0",
+		"nikic/php-parser": "^4.4",
+		"tracy/tracy": "^2.3",
+		"phpstan/phpstan": "^0.12"
+	},
+	"suggest": {
+		"nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()"
+	},
+	"autoload": {
+		"classmap": ["src/"]
+	},
+	"minimum-stability": "dev",
+	"scripts": {
+		"phpstan": "phpstan analyse",
+		"tester": "tester tests -s"
+	},
+	"extra": {
+		"branch-alias": {
+			"dev-master": "3.6-dev"
+		}
+	}
+}

+ 60 - 0
vendor/nette/php-generator/license.md

@@ -0,0 +1,60 @@
+Licenses
+========
+
+Good news! You may use Nette Framework under the terms of either
+the New BSD License or the GNU General Public License (GPL) version 2 or 3.
+
+The BSD License is recommended for most projects. It is easy to understand and it
+places almost no restrictions on what you can do with the framework. If the GPL
+fits better to your project, you can use the framework under this license.
+
+You don't have to notify anyone which license you are using. You can freely
+use Nette Framework in commercial projects as long as the copyright header
+remains intact.
+
+Please be advised that the name "Nette Framework" is a protected trademark and its
+usage has some limitations. So please do not use word "Nette" in the name of your
+project or top-level domain, and choose a name that stands on its own merits.
+If your stuff is good, it will not take long to establish a reputation for yourselves.
+
+
+New BSD License
+---------------
+
+Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+	* Redistributions of source code must retain the above copyright notice,
+	this list of conditions and the following disclaimer.
+
+	* Redistributions in binary form must reproduce the above copyright notice,
+	this list of conditions and the following disclaimer in the documentation
+	and/or other materials provided with the distribution.
+
+	* Neither the name of "Nette Framework" nor the names of its contributors
+	may be used to endorse or promote products derived from this software
+	without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as is" and
+any express or implied warranties, including, but not limited to, the implied
+warranties of merchantability and fitness for a particular purpose are
+disclaimed. In no event shall the copyright owner or contributors be liable for
+any direct, indirect, incidental, special, exemplary, or consequential damages
+(including, but not limited to, procurement of substitute goods or services;
+loss of use, data, or profits; or business interruption) however caused and on
+any theory of liability, whether in contract, strict liability, or tort
+(including negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+
+GNU General Public License
+--------------------------
+
+GPL licenses are very very long, so instead of including them here we offer
+you URLs with full text:
+
+- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
+- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)

+ 736 - 0
vendor/nette/php-generator/readme.md

@@ -0,0 +1,736 @@
+Nette PHP Generator
+===================
+
+[![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator)
+[![Tests](https://github.com/nette/php-generator/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/php-generator/actions)
+[![Coverage Status](https://coveralls.io/repos/github/nette/php-generator/badge.svg?branch=master&v=1)](https://coveralls.io/github/nette/php-generator?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases)
+[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/php-generator/blob/master/license.md)
+
+
+Introduction
+------------
+
+Do you need to generate PHP code of classes, functions, namespaces, etc.? This library with a friendly API will help you.
+
+Documentation can be found on the [website](https://doc.nette.org/php-generator).
+
+
+[Support Me](https://github.com/sponsors/dg)
+--------------------------------------------
+
+Do you like PHP Generator? Are you looking forward to the new features?
+
+[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
+
+Thank you!
+
+
+Installation
+------------
+
+```shell
+composer require nette/php-generator
+```
+
+- PhpGenerator 3.6 is compatible with PHP 7.2 to 8.1
+- PhpGenerator 3.2 – 3.5 is compatible with PHP 7.1 to 8.0
+- PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3
+- PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3
+- PhpGenerator 2.6 is compatible with PHP 5.6 to 7.3
+
+
+
+Classes
+-------
+
+Let's start with a straightforward example of generating class using [ClassType](https://api.nette.org/3.0/Nette/PhpGenerator/ClassType.html):
+
+```php
+$class = new Nette\PhpGenerator\ClassType('Demo');
+
+$class
+	->setFinal()
+	->setExtends(ParentClass::class)
+	->addImplement(Countable::class)
+	->addTrait(Nette\SmartObject::class)
+	->addComment("Description of class.\nSecond line\n")
+	->addComment('@property-read Nette\Forms\Form $form');
+
+// to generate PHP code simply cast to string or use echo:
+echo $class;
+```
+
+It will render this result:
+
+```php
+/**
+ * Description of class.
+ * Second line
+ *
+ * @property-read Nette\Forms\Form $form
+ */
+final class Demo extends ParentClass implements Countable
+{
+	use Nette\SmartObject;
+}
+```
+
+We can also use a printer to generate the code, which, unlike `echo $class`, we will be able to further configure:
+
+```php
+$printer = new Nette\PhpGenerator\Printer;
+echo $printer->printClass($class);
+```
+
+We can add constants ([Constant](https://api.nette.org/3.0/Nette/PhpGenerator/Constant.html)) and properties ([Property](https://api.nette.org/3.0/Nette/PhpGenerator/Property.html)):
+
+```php
+$class->addConstant('ID', 123)
+	->setProtected() // constant visiblity
+	->setFinal();
+
+$class->addProperty('items', [1, 2, 3])
+	->setPrivate() // or setVisibility('private')
+	->setStatic()
+	->addComment('@var int[]');
+
+$class->addProperty('list')
+	->setType('array')
+	->setNullable()
+	->setInitialized(); // prints '= null'
+```
+
+It generates:
+
+```php
+final protected const ID = 123;
+
+/** @var int[] */
+private static $items = [1, 2, 3];
+
+public ?array $list = null;
+```
+
+And we can add methods with parameters:
+
+```php
+$method = $class->addMethod('count')
+	->addComment('Count it.')
+	->addComment('@return int')
+	->setFinal()
+	->setProtected()
+	->setReturnType('int') // method return type
+	->setReturnNullable() // nullable return type
+	->setBody('return count($items ?: $this->items);');
+
+$method->addParameter('items', []) // $items = []
+		->setReference()           // &$items = []
+		->setType('array');        // array &$items = []
+```
+
+It results in:
+
+```php
+/**
+ * Count it.
+ * @return int
+ */
+final protected function count(array &$items = []): ?int
+{
+	return count($items ?: $this->items);
+}
+```
+
+Promoted parameters introduced by PHP 8.0 can be passed to the constructor:
+
+```php
+$method = $class->addMethod('__construct');
+$method->addPromotedParameter('name');
+$method->addPromotedParameter('args', [])
+		->setPrivate();
+```
+
+It results in:
+
+```php
+public function __construct(
+	public $name,
+	private $args = [],
+) {
+}
+```
+
+Readonly properties introduced by PHP 8.1 can be marked via `setReadOnly()`.
+
+------
+
+If the added property, constant, method or parameter already exist, it will be overwritten.
+
+Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`.
+
+You can also add existing `Method`, `Property` or `Constant` objects to the class:
+
+```php
+$method = new Nette\PhpGenerator\Method('getHandle');
+$property = new Nette\PhpGenerator\Property('handle');
+$const = new Nette\PhpGenerator\Constant('ROLE');
+
+$class = (new Nette\PhpGenerator\ClassType('Demo'))
+	->addMember($method)
+	->addMember($property)
+	->addMember($const);
+```
+
+You can clone existing methods, properties and constants with a different name using `cloneWithName()`:
+
+```php
+$methodCount = $class->getMethod('count');
+$methodRecount = $methodCount->cloneWithName('recount');
+$class->addMember($methodRecount);
+```
+
+Types
+-----
+
+Each type or union/intersection type can be passed as a string, you can also use predefined constants for native types:
+
+```php
+use Nette\PhpGenerator\Type;
+
+$member->setType('array'); // or Type::ARRAY;
+$member->setType('array|string'); // or Type::union('array', 'string')
+$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class)
+$member->setType(null); // removes type
+```
+
+The same applies to the method `setReturnType()`.
+
+
+Tabs versus Spaces
+------------------
+
+The generated code uses tabs for indentation. If you want to have the output compatible with PSR-2 or PSR-12, use `PsrPrinter`:
+
+```php
+$class = new Nette\PhpGenerator\ClassType('Demo');
+// ...
+
+$printer = new Nette\PhpGenerator\PsrPrinter;
+echo $printer->printClass($class); // 4 spaces indentation
+```
+
+
+Interface or Trait
+------------------
+
+You can create interfaces and traits:
+
+```php
+$interface = Nette\PhpGenerator\ClassType::interface('MyInterface');
+$trait = Nette\PhpGenerator\ClassType::trait('MyTrait');
+// in a similar way $class = Nette\PhpGenerator\ClassType::class('MyClass');
+```
+
+Enums
+-----
+
+You can easily create the enums that PHP 8.1 brings:
+
+```php
+$enum = Nette\PhpGenerator\ClassType::enum('Suit');
+$enum->addCase('Clubs');
+$enum->addCase('Diamonds');
+$enum->addCase('Hearts');
+$enum->addCase('Spades');
+
+echo $enum;
+```
+
+Result:
+
+```php
+enum Suit
+{
+        case Clubs;
+        case Diamonds;
+        case Hearts;
+        case Spades;
+}
+```
+
+You can also define scalar equivalents for cases to create a backed enum:
+
+```php
+$enum->addCase('Clubs', '♣');
+$enum->addCase('Diamonds', '♦');
+```
+
+It is possible to add a comment or [attributes](#attributes) to each case using `addComment()` or `addAttribute()`.
+
+
+Literals
+--------
+
+With `Literal` you can pass arbitrary PHP code to, for example, default property or parameter values etc:
+
+```php
+use Nette\PhpGenerator\Literal;
+
+$class = new Nette\PhpGenerator\ClassType('Demo');
+
+$class->addProperty('foo', new Literal('Iterator::SELF_FIRST'));
+
+$class->addMethod('bar')
+	->addParameter('id', new Literal('1 + 2'));
+
+echo $class;
+```
+
+Result:
+
+```php
+class Demo
+{
+	public $foo = Iterator::SELF_FIRST;
+
+	public function bar($id = 1 + 2)
+	{
+	}
+}
+```
+
+You can also pass parameters to `Literal` and have it formatted into valid PHP code using [special placeholders](#method-and-function-body-generator):
+
+```php
+new Literal('substr(?, ?)', [$a, $b]);
+// generates, for example: substr('hello', 5);
+```
+
+
+
+Using Traits
+------------
+
+```php
+$class = new Nette\PhpGenerator\ClassType('Demo');
+$class->addTrait('SmartObject');
+$class->addTrait('MyTrait', ['sayHello as protected']);
+echo $class;
+```
+
+Result:
+
+```php
+class Demo
+{
+	use SmartObject;
+	use MyTrait {
+		sayHello as protected;
+	}
+}
+```
+
+Anonymous Class
+---------------
+
+Give `null` as the name and you have an anonymous class:
+
+```php
+$class = new Nette\PhpGenerator\ClassType(null);
+$class->addMethod('__construct')
+	->addParameter('foo');
+
+echo '$obj = new class ($val) ' . $class . ';';
+```
+
+Result:
+
+```php
+$obj = new class ($val) {
+
+	public function __construct($foo)
+	{
+	}
+};
+```
+
+Global Function
+---------------
+
+Code of functions will generate class [GlobalFunction](https://api.nette.org/3.0/Nette/PhpGenerator/GlobalFunction.html):
+
+```php
+$function = new Nette\PhpGenerator\GlobalFunction('foo');
+$function->setBody('return $a + $b;');
+$function->addParameter('a');
+$function->addParameter('b');
+echo $function;
+
+// or use PsrPrinter for output compatible with PSR-2 / PSR-12
+// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
+```
+
+Result:
+
+```php
+function foo($a, $b)
+{
+	return $a + $b;
+}
+```
+
+Closure
+-------
+
+Code of closures will generate class [Closure](https://api.nette.org/3.0/Nette/PhpGenerator/Closure.html):
+
+```php
+$closure = new Nette\PhpGenerator\Closure;
+$closure->setBody('return $a + $b;');
+$closure->addParameter('a');
+$closure->addParameter('b');
+$closure->addUse('c')
+	->setReference();
+echo $closure;
+
+// or use PsrPrinter for output compatible with PSR-2 / PSR-12
+// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
+```
+
+Result:
+
+```php
+function ($a, $b) use (&$c) {
+	return $a + $b;
+}
+```
+
+Arrow Function
+--------------
+
+You can also print closure as arrow function using printer:
+
+```php
+$closure = new Nette\PhpGenerator\Closure;
+$closure->setBody('return $a + $b;');
+$closure->addParameter('a');
+$closure->addParameter('b');
+
+// or use PsrPrinter for output compatible with PSR-2 / PSR-12
+echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
+```
+
+Result:
+
+```php
+fn($a, $b) => $a + $b
+```
+
+Attributes
+----------
+
+You can add PHP 8 attributes to all classes, methods, properties, constants, functions, closures and parameters.
+
+```php
+$class = new Nette\PhpGenerator\ClassType('Demo');
+$class->addAttribute('Deprecated');
+
+$class->addProperty('list')
+	->addAttribute('WithArguments', [1, 2]);
+
+$method = $class->addMethod('count')
+	->addAttribute('Foo\Cached', ['mode' => true]);
+
+$method->addParameter('items')
+	->addAttribute('Bar');
+
+echo $class;
+```
+
+Result:
+
+```php
+#[Deprecated]
+class Demo
+{
+	#[WithArguments(1, 2)]
+	public $list;
+
+
+	#[Foo\Cached(mode: true)]
+	public function count(#[Bar] $items)
+	{
+	}
+}
+```
+
+Method and Function Body Generator
+----------------------------------
+
+The body can be passed to the `setBody()` method at once or sequentially (line by line) by repeatedly calling `addBody()`:
+
+```php
+$function = new Nette\PhpGenerator\GlobalFunction('foo');
+$function->addBody('$a = rand(10, 20);');
+$function->addBody('return $a;');
+echo $function;
+```
+
+Result
+
+```php
+function foo()
+{
+	$a = rand(10, 20);
+	return $a;
+}
+```
+
+You can use special placeholders for handy way to inject variables.
+
+Simple placeholders `?`
+
+```php
+$str = 'any string';
+$num = 3;
+$function = new Nette\PhpGenerator\GlobalFunction('foo');
+$function->addBody('return strlen(?, ?);', [$str, $num]);
+echo $function;
+```
+
+Result:
+
+```php
+function foo()
+{
+	return strlen('any string', 3);
+}
+```
+
+Variadic placeholder `...?`
+
+```php
+$items = [1, 2, 3];
+$function = new Nette\PhpGenerator\GlobalFunction('foo');
+$function->setBody('myfunc(...?);', [$items]);
+echo $function;
+```
+
+Result:
+
+```php
+function foo()
+{
+	myfunc(1, 2, 3);
+}
+```
+
+You can also use PHP 8 named parameters using placeholder `...?:`
+
+```php
+$items = ['foo' => 1, 'bar' => true];
+$function->setBody('myfunc(...?:);', [$items]);
+
+// myfunc(foo: 1, bar: true);
+```
+
+Escape placeholder using slash `\?`
+
+```php
+$num = 3;
+$function = new Nette\PhpGenerator\GlobalFunction('foo');
+$function->addParameter('a');
+$function->addBody('return $a \? 10 : ?;', [$num]);
+echo $function;
+```
+
+Result:
+
+```php
+function foo($a)
+{
+	return $a ? 10 : 3;
+}
+```
+
+Namespace
+---------
+
+Classes, traits and interfaces (hereinafter classes) can be grouped into namespaces ([PhpNamespace](https://api.nette.org/3.0/Nette/PhpGenerator/PhpNamespace.html)):
+
+```php
+$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
+
+// create new classes in the namespace
+$class = $namespace->addClass('Task');
+$interface = $namespace->addInterface('Countable');
+$trait = $namespace->addTrait('NameAware');
+
+// or insert an existing class into the namespace
+$class = new Nette\PhpGenerator\ClassType('Task');
+$namespace->add($class);
+```
+
+If the class already exists, it will be overwritten.
+
+You can define use-statements:
+
+```php
+// use Http\Request;
+$namespace->addUse(Http\Request::class);
+// use Http\Request as HttpReq;
+$namespace->addUse(Http\Request::class, 'HttpReq');
+```
+
+Class Names Resolving
+---------------------
+
+**When the class is part of the namespace, it is rendered slightly differently**: all types (ie. type hints, return types, parent class name,
+implemented interfaces, used traits and attributes) are automatically *resolved* (unless you turn it off, see below).
+It means that you have to **use full class names** in definitions and they will be replaced
+with aliases (according to the use-statements) or fully qualified names in the resulting code:
+
+```php
+$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
+$namespace->addUse('Bar\AliasedClass');
+
+$class = $namespace->addClass('Demo');
+$class->addImplement('Foo\A') // it will resolve to A
+	->addTrait('Bar\AliasedClass'); // it will resolve to AliasedClass
+
+$method = $class->addMethod('method');
+$method->addComment('@return ' . $namespace->unresolveName('Foo\D')); // in comments resolve manually
+$method->addParameter('arg')
+	->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass
+
+echo $namespace;
+
+// or use PsrPrinter for output compatible with PSR-2 / PSR-12
+// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
+```
+
+Result:
+
+```php
+namespace Foo;
+
+use Bar\AliasedClass;
+
+class Demo implements A
+{
+	use AliasedClass;
+
+	/**
+	 * @return D
+	 */
+	public function method(\Bar\OtherClass $arg)
+	{
+	}
+}
+```
+
+Auto-resolving can be turned off this way:
+
+```php
+$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter
+$printer->setTypeResolving(false);
+echo $printer->printNamespace($namespace);
+```
+
+PHP Files
+---------
+
+Classes and namespaces can be grouped into PHP files represented by the class [PhpFile](https://api.nette.org/3.0/Nette/PhpGenerator/PhpFile.html):
+
+```php
+$file = new Nette\PhpGenerator\PhpFile;
+$file->addComment('This file is auto-generated.');
+$file->setStrictTypes(); // adds declare(strict_types=1)
+
+$namespace = $file->addNamespace('Foo');
+$class = $namespace->addClass('A');
+$class->addMethod('hello');
+
+// or insert an existing namespace into the file
+// $file->addNamespace(new Nette\PhpGenerator\PhpNamespace('Foo'));
+
+echo $file;
+
+// or use PsrPrinter for output compatible with PSR-2 / PSR-12
+// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
+```
+
+Result:
+
+```php
+<?php
+
+/**
+ * This file is auto-generated.
+ */
+
+declare(strict_types=1);
+
+namespace Foo;
+
+class A
+{
+	public function hello()
+	{
+	}
+}
+```
+
+Generate using Reflection
+-------------------------
+
+Another common use case is to create class or method based on existing ones:
+
+```php
+$class = Nette\PhpGenerator\ClassType::from(PDO::class);
+
+$function = Nette\PhpGenerator\GlobalFunction::from('trim');
+
+$closure = Nette\PhpGenerator\Closure::from(
+	function (stdClass $a, $b = null) {}
+);
+```
+
+Method bodies are empty by default. If you want to load them as well, use this way
+(it requires `nikic/php-parser` to be installed):
+
+```php
+$class = Nette\PhpGenerator\ClassType::withBodiesFrom(MyClass::class);
+
+$function = Nette\PhpGenerator\GlobalFunction::withBodyFrom('dump');
+```
+
+
+Variables Dumper
+----------------
+
+The Dumper returns a parsable PHP string representation of a variable. Provides better and clearer output that native functon `var_export()`.
+
+```php
+$dumper = new Nette\PhpGenerator\Dumper;
+
+$var = ['a', 'b', 123];
+
+echo $dumper->dump($var); // prints ['a', 'b', 123]
+```
+
+Custom Printer
+--------------
+
+Need to customize printer behavior? Create your own by inheriting the `Printer` class. You can reconfigure these variables:
+
+```php
+class MyPrinter extends Nette\PhpGenerator\Printer
+{
+	protected $indentation = "\t";
+	protected $linesBetweenProperties = 0;
+	protected $linesBetweenMethods = 1;
+	protected $returnTypeColon = ': ';
+}
+```

+ 49 - 0
vendor/nette/php-generator/src/PhpGenerator/Attribute.php

@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * PHP Attribute.
+ */
+final class Attribute
+{
+	use Nette\SmartObject;
+
+	/** @var string */
+	private $name;
+
+	/** @var array */
+	private $args;
+
+
+	public function __construct(string $name, array $args)
+	{
+		if (!Helpers::isNamespaceIdentifier($name)) {
+			throw new Nette\InvalidArgumentException("Value '$name' is not valid attribute name.");
+		}
+		$this->name = $name;
+		$this->args = $args;
+	}
+
+
+	public function getName(): string
+	{
+		return $this->name;
+	}
+
+
+	public function getArguments(): array
+	{
+		return $this->args;
+	}
+}

+ 627 - 0
vendor/nette/php-generator/src/PhpGenerator/ClassType.php

@@ -0,0 +1,627 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Class/Interface/Trait/Enum description.
+ *
+ * @property Method[] $methods
+ * @property Property[] $properties
+ */
+final class ClassType
+{
+	use Nette\SmartObject;
+	use Traits\CommentAware;
+	use Traits\AttributeAware;
+
+	public const
+		TYPE_CLASS = 'class',
+		TYPE_INTERFACE = 'interface',
+		TYPE_TRAIT = 'trait',
+		TYPE_ENUM = 'enum';
+
+	public const
+		VISIBILITY_PUBLIC = 'public',
+		VISIBILITY_PROTECTED = 'protected',
+		VISIBILITY_PRIVATE = 'private';
+
+	/** @var PhpNamespace|null */
+	private $namespace;
+
+	/** @var string|null */
+	private $name;
+
+	/** @var string  class|interface|trait */
+	private $type = self::TYPE_CLASS;
+
+	/** @var bool */
+	private $final = false;
+
+	/** @var bool */
+	private $abstract = false;
+
+	/** @var string|string[] */
+	private $extends = [];
+
+	/** @var string[] */
+	private $implements = [];
+
+	/** @var array[] */
+	private $traits = [];
+
+	/** @var Constant[] name => Constant */
+	private $consts = [];
+
+	/** @var Property[] name => Property */
+	private $properties = [];
+
+	/** @var Method[] name => Method */
+	private $methods = [];
+
+	/** @var EnumCase[] name => EnumCase */
+	private $cases = [];
+
+
+	public static function class(string $name = null, PhpNamespace $namespace = null): self
+	{
+		return new self($name, $namespace);
+	}
+
+
+	public static function interface(string $name = null, PhpNamespace $namespace = null): self
+	{
+		return (new self($name, $namespace))->setType(self::TYPE_INTERFACE);
+	}
+
+
+	public static function trait(string $name = null, PhpNamespace $namespace = null): self
+	{
+		return (new self($name, $namespace))->setType(self::TYPE_TRAIT);
+	}
+
+
+	public static function enum(string $name = null, PhpNamespace $namespace = null): self
+	{
+		return (new self($name, $namespace))->setType(self::TYPE_ENUM);
+	}
+
+
+	/**
+	 * @param  string|object  $class
+	 */
+	public static function from($class): self
+	{
+		return (new Factory)->fromClassReflection(new \ReflectionClass($class));
+	}
+
+
+	/**
+	 * @param  string|object  $class
+	 */
+	public static function withBodiesFrom($class): self
+	{
+		return (new Factory)->fromClassReflection(new \ReflectionClass($class), true);
+	}
+
+
+	public function __construct(string $name = null, PhpNamespace $namespace = null)
+	{
+		$this->setName($name);
+		$this->namespace = $namespace;
+	}
+
+
+	public function __toString(): string
+	{
+		try {
+			return (new Printer)->printClass($this, $this->namespace);
+		} catch (\Throwable $e) {
+			if (PHP_VERSION_ID >= 70400) {
+				throw $e;
+			}
+			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+			return '';
+		}
+	}
+
+
+	/** @deprecated  an object can be in multiple namespaces */
+	public function getNamespace(): ?PhpNamespace
+	{
+		return $this->namespace;
+	}
+
+
+	/** @return static */
+	public function setName(?string $name): self
+	{
+		if ($name !== null && !Helpers::isIdentifier($name)) {
+			throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
+		}
+		$this->name = $name;
+		return $this;
+	}
+
+
+	public function getName(): ?string
+	{
+		return $this->name;
+	}
+
+
+	/** @deprecated */
+	public function setClass(): self
+	{
+		$this->type = self::TYPE_CLASS;
+		return $this;
+	}
+
+
+	public function isClass(): bool
+	{
+		return $this->type === self::TYPE_CLASS;
+	}
+
+
+	/** @return static */
+	public function setInterface(): self
+	{
+		$this->type = self::TYPE_INTERFACE;
+		return $this;
+	}
+
+
+	public function isInterface(): bool
+	{
+		return $this->type === self::TYPE_INTERFACE;
+	}
+
+
+	/** @return static */
+	public function setTrait(): self
+	{
+		$this->type = self::TYPE_TRAIT;
+		return $this;
+	}
+
+
+	public function isTrait(): bool
+	{
+		return $this->type === self::TYPE_TRAIT;
+	}
+
+
+	public function isEnum(): bool
+	{
+		return $this->type === self::TYPE_ENUM;
+	}
+
+
+	/** @return static */
+	public function setType(string $type): self
+	{
+		if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT, self::TYPE_ENUM], true)) {
+			throw new Nette\InvalidArgumentException('Argument must be class|interface|trait|enum.');
+		}
+		$this->type = $type;
+		return $this;
+	}
+
+
+	public function getType(): string
+	{
+		return $this->type;
+	}
+
+
+	/** @return static */
+	public function setFinal(bool $state = true): self
+	{
+		$this->final = $state;
+		return $this;
+	}
+
+
+	public function isFinal(): bool
+	{
+		return $this->final;
+	}
+
+
+	/** @return static */
+	public function setAbstract(bool $state = true): self
+	{
+		$this->abstract = $state;
+		return $this;
+	}
+
+
+	public function isAbstract(): bool
+	{
+		return $this->abstract;
+	}
+
+
+	/**
+	 * @param  string|string[]  $names
+	 * @return static
+	 */
+	public function setExtends($names): self
+	{
+		if (!is_string($names) && !is_array($names)) {
+			throw new Nette\InvalidArgumentException('Argument must be string or string[].');
+		}
+		$this->validateNames((array) $names);
+		$this->extends = $names;
+		return $this;
+	}
+
+
+	/** @return string|string[] */
+	public function getExtends()
+	{
+		return $this->extends;
+	}
+
+
+	/** @return static */
+	public function addExtend(string $name): self
+	{
+		$this->validateNames([$name]);
+		$this->extends = (array) $this->extends;
+		$this->extends[] = $name;
+		return $this;
+	}
+
+
+	/**
+	 * @param  string[]  $names
+	 * @return static
+	 */
+	public function setImplements(array $names): self
+	{
+		$this->validateNames($names);
+		$this->implements = $names;
+		return $this;
+	}
+
+
+	/** @return string[] */
+	public function getImplements(): array
+	{
+		return $this->implements;
+	}
+
+
+	/** @return static */
+	public function addImplement(string $name): self
+	{
+		$this->validateNames([$name]);
+		$this->implements[] = $name;
+		return $this;
+	}
+
+
+	/** @return static */
+	public function removeImplement(string $name): self
+	{
+		$key = array_search($name, $this->implements, true);
+		if ($key !== false) {
+			unset($this->implements[$key]);
+		}
+		return $this;
+	}
+
+
+	/**
+	 * @param  string[]  $names
+	 * @return static
+	 */
+	public function setTraits(array $names): self
+	{
+		$this->validateNames($names);
+		$this->traits = array_fill_keys($names, []);
+		return $this;
+	}
+
+
+	/** @return string[] */
+	public function getTraits(): array
+	{
+		return array_keys($this->traits);
+	}
+
+
+	/** @internal */
+	public function getTraitResolutions(): array
+	{
+		return $this->traits;
+	}
+
+
+	/** @return static */
+	public function addTrait(string $name, array $resolutions = []): self
+	{
+		$this->validateNames([$name]);
+		$this->traits[$name] = $resolutions;
+		return $this;
+	}
+
+
+	/** @return static */
+	public function removeTrait(string $name): self
+	{
+		unset($this->traits[$name]);
+		return $this;
+	}
+
+
+	/**
+	 * @param  Method|Property|Constant|EnumCase  $member
+	 * @return static
+	 */
+	public function addMember($member): self
+	{
+		if ($member instanceof Method) {
+			if ($this->isInterface()) {
+				$member->setBody(null);
+			}
+			$this->methods[$member->getName()] = $member;
+
+		} elseif ($member instanceof Property) {
+			$this->properties[$member->getName()] = $member;
+
+		} elseif ($member instanceof Constant) {
+			$this->consts[$member->getName()] = $member;
+
+		} elseif ($member instanceof EnumCase) {
+			$this->cases[$member->getName()] = $member;
+
+		} else {
+			throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant|EnumCase.');
+		}
+
+		return $this;
+	}
+
+
+	/**
+	 * @param  Constant[]|mixed[]  $consts
+	 * @return static
+	 */
+	public function setConstants(array $consts): self
+	{
+		$this->consts = [];
+		foreach ($consts as $k => $v) {
+			$const = $v instanceof Constant
+				? $v
+				: (new Constant($k))->setValue($v);
+			$this->consts[$const->getName()] = $const;
+		}
+		return $this;
+	}
+
+
+	/** @return Constant[] */
+	public function getConstants(): array
+	{
+		return $this->consts;
+	}
+
+
+	public function addConstant(string $name, $value): Constant
+	{
+		return $this->consts[$name] = (new Constant($name))->setValue($value);
+	}
+
+
+	/** @return static */
+	public function removeConstant(string $name): self
+	{
+		unset($this->consts[$name]);
+		return $this;
+	}
+
+
+	/**
+	 * Sets cases to enum
+	 * @param  EnumCase[]  $consts
+	 * @return static
+	 */
+	public function setCases(array $cases): self
+	{
+		(function (EnumCase ...$cases) {})(...$cases);
+		$this->cases = [];
+		foreach ($cases as $case) {
+			$this->cases[$case->getName()] = $case;
+		}
+		return $this;
+	}
+
+
+	/** @return EnumCase[] */
+	public function getCases(): array
+	{
+		return $this->cases;
+	}
+
+
+	/** Adds case to enum */
+	public function addCase(string $name, $value = null): EnumCase
+	{
+		return $this->cases[$name] = (new EnumCase($name))->setValue($value);
+	}
+
+
+	/** @return static */
+	public function removeCase(string $name): self
+	{
+		unset($this->cases[$name]);
+		return $this;
+	}
+
+
+	/**
+	 * @param  Property[]  $props
+	 * @return static
+	 */
+	public function setProperties(array $props): self
+	{
+		$this->properties = [];
+		foreach ($props as $v) {
+			if (!$v instanceof Property) {
+				throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Property[].');
+			}
+			$this->properties[$v->getName()] = $v;
+		}
+		return $this;
+	}
+
+
+	/** @return Property[] */
+	public function getProperties(): array
+	{
+		return $this->properties;
+	}
+
+
+	public function getProperty(string $name): Property
+	{
+		if (!isset($this->properties[$name])) {
+			throw new Nette\InvalidArgumentException("Property '$name' not found.");
+		}
+		return $this->properties[$name];
+	}
+
+
+	/**
+	 * @param  string  $name  without $
+	 */
+	public function addProperty(string $name, $value = null): Property
+	{
+		return $this->properties[$name] = func_num_args() > 1
+			? (new Property($name))->setValue($value)
+			: new Property($name);
+	}
+
+
+	/**
+	 * @param  string  $name without $
+	 * @return static
+	 */
+	public function removeProperty(string $name): self
+	{
+		unset($this->properties[$name]);
+		return $this;
+	}
+
+
+	public function hasProperty(string $name): bool
+	{
+		return isset($this->properties[$name]);
+	}
+
+
+	/**
+	 * @param  Method[]  $methods
+	 * @return static
+	 */
+	public function setMethods(array $methods): self
+	{
+		$this->methods = [];
+		foreach ($methods as $v) {
+			if (!$v instanceof Method) {
+				throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Method[].');
+			}
+			$this->methods[$v->getName()] = $v;
+		}
+		return $this;
+	}
+
+
+	/** @return Method[] */
+	public function getMethods(): array
+	{
+		return $this->methods;
+	}
+
+
+	public function getMethod(string $name): Method
+	{
+		if (!isset($this->methods[$name])) {
+			throw new Nette\InvalidArgumentException("Method '$name' not found.");
+		}
+		return $this->methods[$name];
+	}
+
+
+	public function addMethod(string $name): Method
+	{
+		$method = new Method($name);
+		if ($this->isInterface()) {
+			$method->setBody(null);
+		} else {
+			$method->setPublic();
+		}
+		return $this->methods[$name] = $method;
+	}
+
+
+	/** @return static */
+	public function removeMethod(string $name): self
+	{
+		unset($this->methods[$name]);
+		return $this;
+	}
+
+
+	public function hasMethod(string $name): bool
+	{
+		return isset($this->methods[$name]);
+	}
+
+
+	/** @throws Nette\InvalidStateException */
+	public function validate(): void
+	{
+		if ($this->abstract && $this->final) {
+			throw new Nette\InvalidStateException('Class cannot be abstract and final.');
+
+		} elseif ($this->isEnum() && ($this->abstract || $this->final || $this->extends || $this->properties)) {
+			throw new Nette\InvalidStateException('Enum cannot be abstract or final or extends class or have properties.');
+
+		} elseif (!$this->name && ($this->abstract || $this->final)) {
+			throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.');
+		}
+	}
+
+
+	private function validateNames(array $names): void
+	{
+		foreach ($names as $name) {
+			if (!Helpers::isNamespaceIdentifier($name, true)) {
+				throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
+			}
+		}
+	}
+
+
+	public function __clone()
+	{
+		$clone = function ($item) { return clone $item; };
+		$this->cases = array_map($clone, $this->cases);
+		$this->consts = array_map($clone, $this->consts);
+		$this->properties = array_map($clone, $this->properties);
+		$this->methods = array_map($clone, $this->methods);
+	}
+}

+ 72 - 0
vendor/nette/php-generator/src/PhpGenerator/Closure.php

@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Closure.
+ *
+ * @property string $body
+ */
+final class Closure
+{
+	use Nette\SmartObject;
+	use Traits\FunctionLike;
+	use Traits\AttributeAware;
+
+	/** @var Parameter[] */
+	private $uses = [];
+
+
+	public static function from(\Closure $closure): self
+	{
+		return (new Factory)->fromFunctionReflection(new \ReflectionFunction($closure));
+	}
+
+
+	public function __toString(): string
+	{
+		try {
+			return (new Printer)->printClosure($this);
+		} catch (\Throwable $e) {
+			if (PHP_VERSION_ID >= 70400) {
+				throw $e;
+			}
+			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+			return '';
+		}
+	}
+
+
+	/**
+	 * @param  Parameter[]  $uses
+	 * @return static
+	 */
+	public function setUses(array $uses): self
+	{
+		(function (Parameter ...$uses) {})(...$uses);
+		$this->uses = $uses;
+		return $this;
+	}
+
+
+	public function getUses(): array
+	{
+		return $this->uses;
+	}
+
+
+	public function addUse(string $name): Parameter
+	{
+		return $this->uses[] = new Parameter($name);
+	}
+}

+ 59 - 0
vendor/nette/php-generator/src/PhpGenerator/Constant.php

@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Class constant.
+ */
+final class Constant
+{
+	use Nette\SmartObject;
+	use Traits\NameAware;
+	use Traits\VisibilityAware;
+	use Traits\CommentAware;
+	use Traits\AttributeAware;
+
+	/** @var mixed */
+	private $value;
+
+	/** @var bool */
+	private $final = false;
+
+
+	/** @return static */
+	public function setValue($val): self
+	{
+		$this->value = $val;
+		return $this;
+	}
+
+
+	public function getValue()
+	{
+		return $this->value;
+	}
+
+
+	/** @return static */
+	public function setFinal(bool $state = true): self
+	{
+		$this->final = $state;
+		return $this;
+	}
+
+
+	public function isFinal(): bool
+	{
+		return $this->final;
+	}
+}

+ 240 - 0
vendor/nette/php-generator/src/PhpGenerator/Dumper.php

@@ -0,0 +1,240 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * PHP code generator utils.
+ */
+final class Dumper
+{
+	private const INDENT_LENGTH = 4;
+
+	/** @var int */
+	public $maxDepth = 50;
+
+	/** @var int */
+	public $wrapLength = 120;
+
+
+	/**
+	 * Returns a PHP representation of a variable.
+	 */
+	public function dump($var, int $column = 0): string
+	{
+		return $this->dumpVar($var, [], 0, $column);
+	}
+
+
+	private function dumpVar(&$var, array $parents = [], int $level = 0, int $column = 0): string
+	{
+		if ($var === null) {
+			return 'null';
+
+		} elseif (is_string($var)) {
+			return $this->dumpString($var);
+
+		} elseif (is_array($var)) {
+			return $this->dumpArray($var, $parents, $level, $column);
+
+		} elseif (is_object($var)) {
+			if ($var instanceof Literal || $var instanceof Closure) {
+				return ltrim(Nette\Utils\Strings::indent(trim((string) $var), $level), "\t");
+			}
+			return $this->dumpObject($var, $parents, $level);
+
+		} elseif (is_resource($var)) {
+			throw new Nette\InvalidArgumentException('Cannot dump resource.');
+
+		} else {
+			return var_export($var, true);
+		}
+	}
+
+
+	private function dumpString(string $var): string
+	{
+		if (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error()) {
+			static $table;
+			if ($table === null) {
+				foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
+					$table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
+				}
+				$table['\\'] = '\\\\';
+				$table["\r"] = '\r';
+				$table["\n"] = '\n';
+				$table["\t"] = '\t';
+				$table['$'] = '\$';
+				$table['"'] = '\"';
+			}
+			return '"' . strtr($var, $table) . '"';
+		}
+
+		return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $var) . "'";
+	}
+
+
+	private function dumpArray(array &$var, array $parents, int $level, int $column): string
+	{
+		if (empty($var)) {
+			return '[]';
+
+		} elseif ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) {
+			throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
+		}
+
+		$space = str_repeat("\t", $level);
+		$outInline = '';
+		$outWrapped = "\n$space";
+		$parents[] = $var;
+		$counter = 0;
+		$hideKeys = is_int(($tmp = array_keys($var))[0]) && $tmp === range($tmp[0], $tmp[0] + count($var) - 1);
+
+		foreach ($var as $k => &$v) {
+			$keyPart = $hideKeys && $k === $counter
+				? ''
+				: $this->dumpVar($k) . ' => ';
+			$counter = is_int($k) ? max($k + 1, $counter) : $counter;
+			$outInline .= ($outInline === '' ? '' : ', ') . $keyPart;
+			$outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline));
+			$outWrapped .= "\t"
+				. $keyPart
+				. $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
+				. ",\n$space";
+		}
+
+		array_pop($parents);
+		$wrap = strpos($outInline, "\n") !== false || $level * self::INDENT_LENGTH + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [],
+		return '[' . ($wrap ? $outWrapped : $outInline) . ']';
+	}
+
+
+	private function dumpObject(&$var, array $parents, int $level): string
+	{
+		if ($var instanceof \Serializable) {
+			return 'unserialize(' . $this->dumpString(serialize($var)) . ')';
+
+		} elseif ($var instanceof \UnitEnum) {
+			return '\\' . get_class($var) . '::' . $var->name;
+
+		} elseif ($var instanceof \Closure) {
+			$inner = Nette\Utils\Callback::unwrap($var);
+			if (Nette\Utils\Callback::isStatic($inner)) {
+				return '\Closure::fromCallable(' . $this->dump($inner) . ')';
+			}
+			throw new Nette\InvalidArgumentException('Cannot dump closure.');
+		}
+
+		$class = get_class($var);
+		if ((new \ReflectionObject($var))->isAnonymous()) {
+			throw new Nette\InvalidArgumentException('Cannot dump anonymous class.');
+
+		} elseif (in_array($class, [\DateTime::class, \DateTimeImmutable::class], true)) {
+			return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName());
+		}
+
+		$arr = (array) $var;
+		$space = str_repeat("\t", $level);
+
+		if ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) {
+			throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
+		}
+
+		$out = "\n";
+		$parents[] = $var;
+		if (method_exists($var, '__sleep')) {
+			foreach ($var->__sleep() as $v) {
+				$props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true;
+			}
+		}
+
+		foreach ($arr as $k => &$v) {
+			if (!isset($props) || isset($props[$k])) {
+				$out .= "$space\t"
+					. ($keyPart = $this->dumpVar($k) . ' => ')
+					. $this->dumpVar($v, $parents, $level + 1, strlen($keyPart))
+					. ",\n";
+			}
+		}
+
+		array_pop($parents);
+		$out .= $space;
+		return $class === \stdClass::class
+			? "(object) [$out]"
+			: '\\' . self::class . "::createObject('$class', [$out])";
+	}
+
+
+	/**
+	 * Generates PHP statement.
+	 */
+	public function format(string $statement, ...$args): string
+	{
+		$tokens = preg_split('#(\.\.\.\?:?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE);
+		$res = '';
+		foreach ($tokens as $n => $token) {
+			if ($n % 2 === 0) {
+				$res .= $token;
+			} elseif ($token === '\\?') {
+				$res .= '?';
+			} elseif (!$args) {
+				throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
+			} elseif ($token === '?') {
+				$res .= $this->dump(array_shift($args), strlen($res) - strrpos($res, "\n"));
+			} elseif ($token === '...?' || $token === '...?:' || $token === '?*') {
+				$arg = array_shift($args);
+				if (!is_array($arg)) {
+					throw new Nette\InvalidArgumentException('Argument must be an array.');
+				}
+				$res .= $this->dumpArguments($arg, strlen($res) - strrpos($res, "\n"), $token === '...?:');
+
+			} else { // $  ->  ::
+				$arg = array_shift($args);
+				if ($arg instanceof Literal || !Helpers::isIdentifier($arg)) {
+					$arg = '{' . $this->dumpVar($arg) . '}';
+				}
+				$res .= substr($token, 0, -1) . $arg;
+			}
+		}
+		if ($args) {
+			throw new Nette\InvalidArgumentException('Insufficient number of placeholders.');
+		}
+		return $res;
+	}
+
+
+	private function dumpArguments(array &$var, int $column, bool $named): string
+	{
+		$outInline = $outWrapped = '';
+
+		foreach ($var as $k => &$v) {
+			$k = !$named || is_int($k) ? '' : $k . ': ';
+			$outInline .= $outInline === '' ? '' : ', ';
+			$outInline .= $k . $this->dumpVar($v, [$var], 0, $column + strlen($outInline));
+			$outWrapped .= ($outWrapped === '' ? '' : ',') . "\n\t" . $k . $this->dumpVar($v, [$var], 1);
+		}
+
+		return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength)
+			? $outWrapped . "\n"
+			: $outInline;
+	}
+
+
+	/**
+	 * @return object
+	 * @internal
+	 */
+	public static function createObject(string $class, array $props)
+	{
+		return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1));
+	}
+}

+ 41 - 0
vendor/nette/php-generator/src/PhpGenerator/EnumCase.php

@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Enum case.
+ */
+final class EnumCase
+{
+	use Nette\SmartObject;
+	use Traits\NameAware;
+	use Traits\CommentAware;
+	use Traits\AttributeAware;
+
+	/** @var mixed */
+	private $value;
+
+
+	/** @return static */
+	public function setValue($val): self
+	{
+		$this->value = $val;
+		return $this;
+	}
+
+
+	public function getValue()
+	{
+		return $this->value;
+	}
+}

+ 411 - 0
vendor/nette/php-generator/src/PhpGenerator/Factory.php

@@ -0,0 +1,411 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+use PhpParser;
+use PhpParser\Node;
+use PhpParser\ParserFactory;
+
+
+/**
+ * Creates a representation based on reflection.
+ */
+final class Factory
+{
+	use Nette\SmartObject;
+
+	public function fromClassReflection(\ReflectionClass $from, bool $withBodies = false): ClassType
+	{
+		$class = $from->isAnonymous()
+			? new ClassType
+			: new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
+
+		if (PHP_VERSION_ID >= 80100 && $from->isEnum()) {
+			$class->setType($class::TYPE_ENUM);
+			$from = new \ReflectionEnum($from->getName());
+			$enumIface = $from->isBacked() ? \BackedEnum::class : \UnitEnum::class;
+		} else {
+			$class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
+			$class->setFinal($from->isFinal() && $class->isClass());
+			$class->setAbstract($from->isAbstract() && $class->isClass());
+			$enumIface = null;
+		}
+
+		$ifaces = $from->getInterfaceNames();
+		foreach ($ifaces as $iface) {
+			$ifaces = array_filter($ifaces, function (string $item) use ($iface): bool {
+				return !is_subclass_of($iface, $item);
+			});
+		}
+		if ($from->isInterface()) {
+			$class->setExtends($ifaces);
+		} else {
+			$ifaces = array_diff($ifaces, [$enumIface]);
+			$class->setImplements($ifaces);
+		}
+
+		$class->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
+		$class->setAttributes(self::getAttributes($from));
+		if ($from->getParentClass()) {
+			$class->setExtends($from->getParentClass()->name);
+			$class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
+		}
+
+		$props = [];
+		foreach ($from->getProperties() as $prop) {
+			if ($prop->isDefault()
+				&& $prop->getDeclaringClass()->name === $from->name
+				&& (PHP_VERSION_ID < 80000 || !$prop->isPromoted())
+				&& !$class->isEnum()
+			) {
+				$props[] = $this->fromPropertyReflection($prop);
+			}
+		}
+		$class->setProperties($props);
+
+		$methods = $bodies = [];
+		foreach ($from->getMethods() as $method) {
+			if (
+				$method->getDeclaringClass()->name === $from->name
+				&& (!$enumIface || !method_exists($enumIface, $method->name))
+			) {
+				$methods[] = $m = $this->fromMethodReflection($method);
+				if ($withBodies) {
+					$srcMethod = Nette\Utils\Reflection::getMethodDeclaringMethod($method);
+					$srcClass = $srcMethod->getDeclaringClass()->name;
+					$b = $bodies[$srcClass] = $bodies[$srcClass] ?? $this->loadMethodBodies($srcMethod->getDeclaringClass());
+					if (isset($b[$srcMethod->name])) {
+						$m->setBody($b[$srcMethod->name]);
+					}
+				}
+			}
+		}
+		$class->setMethods($methods);
+
+		$consts = $cases = [];
+		foreach ($from->getReflectionConstants() as $const) {
+			if ($class->isEnum() && $from->hasCase($const->name)) {
+				$cases[] = $this->fromCaseReflection($const);
+			} elseif ($const->getDeclaringClass()->name === $from->name) {
+				$consts[] = $this->fromConstantReflection($const);
+			}
+		}
+		$class->setConstants($consts);
+		$class->setCases($cases);
+
+		return $class;
+	}
+
+
+	public function fromMethodReflection(\ReflectionMethod $from): Method
+	{
+		$method = new Method($from->name);
+		$method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
+		$method->setStatic($from->isStatic());
+		$isInterface = $from->getDeclaringClass()->isInterface();
+		$method->setVisibility(
+			$from->isPrivate()
+				? ClassType::VISIBILITY_PRIVATE
+				: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ($isInterface ? null : ClassType::VISIBILITY_PUBLIC))
+		);
+		$method->setFinal($from->isFinal());
+		$method->setAbstract($from->isAbstract() && !$isInterface);
+		$method->setBody($from->isAbstract() ? null : '');
+		$method->setReturnReference($from->returnsReference());
+		$method->setVariadic($from->isVariadic());
+		$method->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
+		$method->setAttributes(self::getAttributes($from));
+		if ($from->getReturnType() instanceof \ReflectionNamedType) {
+			$method->setReturnType($from->getReturnType()->getName());
+			$method->setReturnNullable($from->getReturnType()->allowsNull());
+		} elseif (
+			$from->getReturnType() instanceof \ReflectionUnionType
+			|| $from->getReturnType() instanceof \ReflectionIntersectionType
+		) {
+			$method->setReturnType((string) $from->getReturnType());
+		}
+		return $method;
+	}
+
+
+	/** @return GlobalFunction|Closure */
+	public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody = false)
+	{
+		$function = $from->isClosure() ? new Closure : new GlobalFunction($from->name);
+		$function->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
+		$function->setReturnReference($from->returnsReference());
+		$function->setVariadic($from->isVariadic());
+		if (!$from->isClosure()) {
+			$function->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
+		}
+		$function->setAttributes(self::getAttributes($from));
+		if ($from->getReturnType() instanceof \ReflectionNamedType) {
+			$function->setReturnType($from->getReturnType()->getName());
+			$function->setReturnNullable($from->getReturnType()->allowsNull());
+		} elseif (
+			$from->getReturnType() instanceof \ReflectionUnionType
+			|| $from->getReturnType() instanceof \ReflectionIntersectionType
+		) {
+			$function->setReturnType((string) $from->getReturnType());
+		}
+		$function->setBody($withBody ? $this->loadFunctionBody($from) : '');
+		return $function;
+	}
+
+
+	/** @return Method|GlobalFunction|Closure */
+	public function fromCallable(callable $from)
+	{
+		$ref = Nette\Utils\Callback::toReflection($from);
+		return $ref instanceof \ReflectionMethod
+			? self::fromMethodReflection($ref)
+			: self::fromFunctionReflection($ref);
+	}
+
+
+	public function fromParameterReflection(\ReflectionParameter $from): Parameter
+	{
+		$param = PHP_VERSION_ID >= 80000 && $from->isPromoted()
+			? new PromotedParameter($from->name)
+			: new Parameter($from->name);
+		$param->setReference($from->isPassedByReference());
+		if ($from->getType() instanceof \ReflectionNamedType) {
+			$param->setType($from->getType()->getName());
+			$param->setNullable($from->getType()->allowsNull());
+		} elseif (
+			$from->getType() instanceof \ReflectionUnionType
+			|| $from->getType() instanceof \ReflectionIntersectionType
+		) {
+			$param->setType((string) $from->getType());
+		}
+		if ($from->isDefaultValueAvailable()) {
+			$param->setDefaultValue($from->isDefaultValueConstant()
+				? new Literal($from->getDefaultValueConstantName())
+				: $from->getDefaultValue());
+			$param->setNullable($param->isNullable() && $param->getDefaultValue() !== null);
+		}
+		$param->setAttributes(self::getAttributes($from));
+		return $param;
+	}
+
+
+	public function fromConstantReflection(\ReflectionClassConstant $from): Constant
+	{
+		$const = new Constant($from->name);
+		$const->setValue($from->getValue());
+		$const->setVisibility(
+			$from->isPrivate()
+				? ClassType::VISIBILITY_PRIVATE
+				: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC)
+		);
+		$const->setFinal(PHP_VERSION_ID >= 80100 ? $from->isFinal() : false);
+		$const->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
+		$const->setAttributes(self::getAttributes($from));
+		return $const;
+	}
+
+
+	public function fromCaseReflection(\ReflectionClassConstant $from): EnumCase
+	{
+		$const = new EnumCase($from->name);
+		$const->setValue($from->getValue()->value ?? null);
+		$const->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
+		$const->setAttributes(self::getAttributes($from));
+		return $const;
+	}
+
+
+	public function fromPropertyReflection(\ReflectionProperty $from): Property
+	{
+		$defaults = $from->getDeclaringClass()->getDefaultProperties();
+		$prop = new Property($from->name);
+		$prop->setValue($defaults[$prop->getName()] ?? null);
+		$prop->setStatic($from->isStatic());
+		$prop->setVisibility(
+			$from->isPrivate()
+				? ClassType::VISIBILITY_PRIVATE
+				: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC)
+		);
+		if (PHP_VERSION_ID >= 70400) {
+			if ($from->getType() instanceof \ReflectionNamedType) {
+				$prop->setType($from->getType()->getName());
+				$prop->setNullable($from->getType()->allowsNull());
+			} elseif (
+				$from->getType() instanceof \ReflectionUnionType
+				|| $from->getType() instanceof \ReflectionIntersectionType
+			) {
+				$prop->setType((string) $from->getType());
+			}
+			$prop->setInitialized($from->hasType() && array_key_exists($prop->getName(), $defaults));
+			$prop->setReadOnly(PHP_VERSION_ID >= 80100 ? $from->isReadOnly() : false);
+		} else {
+			$prop->setInitialized(false);
+		}
+		$prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
+		$prop->setAttributes(self::getAttributes($from));
+		return $prop;
+	}
+
+
+	private function loadMethodBodies(\ReflectionClass $from): array
+	{
+		if ($from->isAnonymous()) {
+			throw new Nette\NotSupportedException('Anonymous classes are not supported.');
+		}
+
+		[$code, $stmts] = $this->parse($from);
+		$nodeFinder = new PhpParser\NodeFinder;
+		$class = $nodeFinder->findFirst($stmts, function (Node $node) use ($from) {
+			return ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) && $node->namespacedName->toString() === $from->name;
+		});
+
+		$bodies = [];
+		foreach ($nodeFinder->findInstanceOf($class, Node\Stmt\ClassMethod::class) as $method) {
+			/** @var Node\Stmt\ClassMethod $method */
+			if ($method->stmts) {
+				$body = $this->extractBody($nodeFinder, $code, $method->stmts);
+				$bodies[$method->name->toString()] = Helpers::unindent($body, 2);
+			}
+		}
+		return $bodies;
+	}
+
+
+	private function loadFunctionBody(\ReflectionFunction $from): string
+	{
+		if ($from->isClosure()) {
+			throw new Nette\NotSupportedException('Closures are not supported.');
+		}
+
+		[$code, $stmts] = $this->parse($from);
+
+		$nodeFinder = new PhpParser\NodeFinder;
+		/** @var Node\Stmt\Function_ $function */
+		$function = $nodeFinder->findFirst($stmts, function (Node $node) use ($from) {
+			return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $from->name;
+		});
+
+		$body = $this->extractBody($nodeFinder, $code, $function->stmts);
+		return Helpers::unindent($body, 1);
+	}
+
+
+	/**
+	 * @param  Node[]  $statements
+	 */
+	private function extractBody(PhpParser\NodeFinder $nodeFinder, string $originalCode, array $statements): string
+	{
+		$start = $statements[0]->getAttribute('startFilePos');
+		$body = substr($originalCode, $start, end($statements)->getAttribute('endFilePos') - $start + 1);
+
+		$replacements = [];
+		// name-nodes => resolved fully-qualified name
+		foreach ($nodeFinder->findInstanceOf($statements, Node\Name::class) as $node) {
+			if ($node->hasAttribute('resolvedName')
+				&& $node->getAttribute('resolvedName') instanceof Node\Name\FullyQualified
+			) {
+				$replacements[] = [
+					$node->getStartFilePos(),
+					$node->getEndFilePos(),
+					$node->getAttribute('resolvedName')->toCodeString(),
+				];
+			}
+		}
+
+		// multi-line strings => singleline
+		foreach (array_merge(
+			$nodeFinder->findInstanceOf($statements, Node\Scalar\String_::class),
+			$nodeFinder->findInstanceOf($statements, Node\Scalar\EncapsedStringPart::class)
+		) as $node) {
+			/** @var Node\Scalar\String_|Node\Scalar\EncapsedStringPart $node */
+			$token = substr($body, $node->getStartFilePos() - $start, $node->getEndFilePos() - $node->getStartFilePos() + 1);
+			if (strpos($token, "\n") !== false) {
+				$quote = $node instanceof Node\Scalar\String_ ? '"' : '';
+				$replacements[] = [
+					$node->getStartFilePos(),
+					$node->getEndFilePos(),
+					$quote . addcslashes($node->value, "\x00..\x1F") . $quote,
+				];
+			}
+		}
+
+		// HEREDOC => "string"
+		foreach ($nodeFinder->findInstanceOf($statements, Node\Scalar\Encapsed::class) as $node) {
+			/** @var Node\Scalar\Encapsed $node */
+			if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) {
+				$replacements[] = [
+					$node->getStartFilePos(),
+					$node->parts[0]->getStartFilePos() - 1,
+					'"',
+				];
+				$replacements[] = [
+					end($node->parts)->getEndFilePos() + 1,
+					$node->getEndFilePos(),
+					'"',
+				];
+			}
+		}
+
+		//sort collected resolved names by position in file
+		usort($replacements, function ($a, $b) {
+			return $a[0] <=> $b[0];
+		});
+		$correctiveOffset = -$start;
+		//replace changes body length so we need correct offset
+		foreach ($replacements as [$startPos, $endPos, $replacement]) {
+			$replacingStringLength = $endPos - $startPos + 1;
+			$body = substr_replace(
+				$body,
+				$replacement,
+				$correctiveOffset + $startPos,
+				$replacingStringLength
+			);
+			$correctiveOffset += strlen($replacement) - $replacingStringLength;
+		}
+		return $body;
+	}
+
+
+	private function parse($from): array
+	{
+		$file = $from->getFileName();
+		if (!class_exists(ParserFactory::class)) {
+			throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser'.");
+		} elseif (!$file) {
+			throw new Nette\InvalidStateException("Source code of $from->name not found.");
+		}
+
+		$lexer = new PhpParser\Lexer(['usedAttributes' => ['startFilePos', 'endFilePos']]);
+		$parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer);
+		$code = file_get_contents($file);
+		$code = str_replace("\r\n", "\n", $code);
+		$stmts = $parser->parse($code);
+
+		$traverser = new PhpParser\NodeTraverser;
+		$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['replaceNodes' => false]));
+		$stmts = $traverser->traverse($stmts);
+
+		return [$code, $stmts];
+	}
+
+
+	private function getAttributes($from): array
+	{
+		if (PHP_VERSION_ID < 80000) {
+			return [];
+		}
+		$res = [];
+		foreach ($from->getAttributes() as $attr) {
+			$res[] = new Attribute($attr->getName(), $attr->getArguments());
+		}
+		return $res;
+	}
+}

+ 52 - 0
vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php

@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Global function.
+ *
+ * @property string $body
+ */
+final class GlobalFunction
+{
+	use Nette\SmartObject;
+	use Traits\FunctionLike;
+	use Traits\NameAware;
+	use Traits\CommentAware;
+	use Traits\AttributeAware;
+
+	public static function from(string $function): self
+	{
+		return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function));
+	}
+
+
+	public static function withBodyFrom(string $function): self
+	{
+		return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), true);
+	}
+
+
+	public function __toString(): string
+	{
+		try {
+			return (new Printer)->printFunction($this);
+		} catch (\Throwable $e) {
+			if (PHP_VERSION_ID >= 70400) {
+				throw $e;
+			}
+			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+			return '';
+		}
+	}
+}

+ 108 - 0
vendor/nette/php-generator/src/PhpGenerator/Helpers.php

@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * @internal
+ */
+final class Helpers
+{
+	use Nette\StaticClass;
+
+	public const PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
+
+
+	/** @deprecated  use (new Nette\PhpGenerator\Dumper)->dump() */
+	public static function dump($var): string
+	{
+		return (new Dumper)->dump($var);
+	}
+
+
+	/** @deprecated  use (new Nette\PhpGenerator\Dumper)->format() */
+	public static function format(string $statement, ...$args): string
+	{
+		return (new Dumper)->format($statement, ...$args);
+	}
+
+
+	/** @deprecated  use (new Nette\PhpGenerator\Dumper)->format() */
+	public static function formatArgs(string $statement, array $args): string
+	{
+		return (new Dumper)->format($statement, ...$args);
+	}
+
+
+	public static function formatDocComment(string $content): string
+	{
+		if (($s = trim($content)) === '') {
+			return '';
+		} elseif (strpos($content, "\n") === false) {
+			return "/** $s */\n";
+		} else {
+			return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n";
+		}
+	}
+
+
+	public static function unformatDocComment(string $comment): string
+	{
+		return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*')));
+	}
+
+
+	public static function unindent(string $s, int $level = 1): string
+	{
+		return preg_replace('#^(\t|\ \ \ \ ){1,' . $level . '}#m', '', $s);
+	}
+
+
+	public static function isIdentifier($value): bool
+	{
+		return is_string($value) && preg_match('#^' . self::PHP_IDENT . '$#D', $value);
+	}
+
+
+	public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool
+	{
+		$re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*$#D';
+		return is_string($value) && preg_match($re, $value);
+	}
+
+
+	public static function extractNamespace(string $name): string
+	{
+		return ($pos = strrpos($name, '\\')) ? substr($name, 0, $pos) : '';
+	}
+
+
+	public static function extractShortName(string $name): string
+	{
+		return ($pos = strrpos($name, '\\')) === false
+			? $name
+			: substr($name, $pos + 1);
+	}
+
+
+	public static function tabsToSpaces(string $s, int $count = 4): string
+	{
+		return str_replace("\t", str_repeat(' ', $count), $s);
+	}
+
+
+	/** @internal */
+	public static function createObject(string $class, array $props)
+	{
+		return Dumper::createObject($class, $props);
+	}
+}

+ 34 - 0
vendor/nette/php-generator/src/PhpGenerator/Literal.php

@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+
+/**
+ * PHP literal value.
+ */
+class Literal
+{
+	/** @var string */
+	private $value;
+
+
+	public function __construct(string $value, array $args = null)
+	{
+		$this->value = $args === null
+			? $value
+			: (new Dumper)->format($value, ...$args);
+	}
+
+
+	public function __toString(): string
+	{
+		return $this->value;
+	}
+}

+ 143 - 0
vendor/nette/php-generator/src/PhpGenerator/Method.php

@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Class method.
+ *
+ * @property string|null $body
+ */
+final class Method
+{
+	use Nette\SmartObject;
+	use Traits\FunctionLike;
+	use Traits\NameAware;
+	use Traits\VisibilityAware;
+	use Traits\CommentAware;
+	use Traits\AttributeAware;
+
+	/** @var string|null */
+	private $body = '';
+
+	/** @var bool */
+	private $static = false;
+
+	/** @var bool */
+	private $final = false;
+
+	/** @var bool */
+	private $abstract = false;
+
+
+	/**
+	 * @param  string|array  $method
+	 */
+	public static function from($method): self
+	{
+		return (new Factory)->fromMethodReflection(Nette\Utils\Callback::toReflection($method));
+	}
+
+
+	public function __toString(): string
+	{
+		try {
+			return (new Printer)->printMethod($this);
+		} catch (\Throwable $e) {
+			if (PHP_VERSION_ID >= 70400) {
+				throw $e;
+			}
+			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+			return '';
+		}
+	}
+
+
+	/** @return static */
+	public function setBody(?string $code, array $args = null): self
+	{
+		$this->body = $args === null || $code === null
+			? $code
+			: (new Dumper)->format($code, ...$args);
+		return $this;
+	}
+
+
+	public function getBody(): ?string
+	{
+		return $this->body;
+	}
+
+
+	/** @return static */
+	public function setStatic(bool $state = true): self
+	{
+		$this->static = $state;
+		return $this;
+	}
+
+
+	public function isStatic(): bool
+	{
+		return $this->static;
+	}
+
+
+	/** @return static */
+	public function setFinal(bool $state = true): self
+	{
+		$this->final = $state;
+		return $this;
+	}
+
+
+	public function isFinal(): bool
+	{
+		return $this->final;
+	}
+
+
+	/** @return static */
+	public function setAbstract(bool $state = true): self
+	{
+		$this->abstract = $state;
+		return $this;
+	}
+
+
+	public function isAbstract(): bool
+	{
+		return $this->abstract;
+	}
+
+
+	/**
+	 * @param  string  $name without $
+	 */
+	public function addPromotedParameter(string $name, $defaultValue = null): PromotedParameter
+	{
+		$param = new PromotedParameter($name);
+		if (func_num_args() > 1) {
+			$param->setDefaultValue($defaultValue);
+		}
+		return $this->parameters[$name] = $param;
+	}
+
+
+	/** @throws Nette\InvalidStateException */
+	public function validate(): void
+	{
+		if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) {
+			throw new Nette\InvalidStateException('Method cannot be abstract and final or private.');
+		}
+	}
+}

+ 134 - 0
vendor/nette/php-generator/src/PhpGenerator/Parameter.php

@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Function/Method parameter description.
+ *
+ * @property mixed $defaultValue
+ */
+class Parameter
+{
+	use Nette\SmartObject;
+	use Traits\NameAware;
+	use Traits\AttributeAware;
+
+	/** @var bool */
+	private $reference = false;
+
+	/** @var string|null */
+	private $type;
+
+	/** @var bool */
+	private $nullable = false;
+
+	/** @var bool */
+	private $hasDefaultValue = false;
+
+	/** @var mixed */
+	private $defaultValue;
+
+
+	/** @return static */
+	public function setReference(bool $state = true): self
+	{
+		$this->reference = $state;
+		return $this;
+	}
+
+
+	public function isReference(): bool
+	{
+		return $this->reference;
+	}
+
+
+	/** @return static */
+	public function setType(?string $type): self
+	{
+		if ($type && $type[0] === '?') {
+			$type = substr($type, 1);
+			$this->nullable = true;
+		}
+		$this->type = $type;
+		return $this;
+	}
+
+
+	public function getType(): ?string
+	{
+		return $this->type;
+	}
+
+
+	/** @deprecated  use setType() */
+	public function setTypeHint(?string $type): self
+	{
+		$this->type = $type;
+		return $this;
+	}
+
+
+	/** @deprecated  use getType() */
+	public function getTypeHint(): ?string
+	{
+		return $this->type;
+	}
+
+
+	/**
+	 * @deprecated  just use setDefaultValue()
+	 * @return static
+	 */
+	public function setOptional(bool $state = true): self
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED);
+		$this->hasDefaultValue = $state;
+		return $this;
+	}
+
+
+	/** @return static */
+	public function setNullable(bool $state = true): self
+	{
+		$this->nullable = $state;
+		return $this;
+	}
+
+
+	public function isNullable(): bool
+	{
+		return $this->nullable;
+	}
+
+
+	/** @return static */
+	public function setDefaultValue($val): self
+	{
+		$this->defaultValue = $val;
+		$this->hasDefaultValue = true;
+		return $this;
+	}
+
+
+	public function getDefaultValue()
+	{
+		return $this->defaultValue;
+	}
+
+
+	public function hasDefaultValue(): bool
+	{
+		return $this->hasDefaultValue;
+	}
+}

+ 138 - 0
vendor/nette/php-generator/src/PhpGenerator/PhpFile.php

@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Instance of PHP file.
+ *
+ * Generates:
+ * - opening tag (<?php)
+ * - doc comments
+ * - one or more namespaces
+ */
+final class PhpFile
+{
+	use Nette\SmartObject;
+	use Traits\CommentAware;
+
+	/** @var PhpNamespace[] */
+	private $namespaces = [];
+
+	/** @var bool */
+	private $strictTypes = false;
+
+
+	public function addClass(string $name): ClassType
+	{
+		return $this
+			->addNamespace(Helpers::extractNamespace($name))
+			->addClass(Helpers::extractShortName($name));
+	}
+
+
+	public function addInterface(string $name): ClassType
+	{
+		return $this
+			->addNamespace(Helpers::extractNamespace($name))
+			->addInterface(Helpers::extractShortName($name));
+	}
+
+
+	public function addTrait(string $name): ClassType
+	{
+		return $this
+			->addNamespace(Helpers::extractNamespace($name))
+			->addTrait(Helpers::extractShortName($name));
+	}
+
+
+	public function addEnum(string $name): ClassType
+	{
+		return $this
+			->addNamespace(Helpers::extractNamespace($name))
+			->addEnum(Helpers::extractShortName($name));
+	}
+
+
+	/** @param  string|PhpNamespace  $namespace */
+	public function addNamespace($namespace): PhpNamespace
+	{
+		if ($namespace instanceof PhpNamespace) {
+			$res = $this->namespaces[$namespace->getName()] = $namespace;
+
+		} elseif (is_string($namespace)) {
+			$res = $this->namespaces[$namespace] = $this->namespaces[$namespace] ?? new PhpNamespace($namespace);
+
+		} else {
+			throw new Nette\InvalidArgumentException('Argument must be string|PhpNamespace.');
+		}
+
+		foreach ($this->namespaces as $namespace) {
+			$namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces['']));
+		}
+		return $res;
+	}
+
+
+	/** @return PhpNamespace[] */
+	public function getNamespaces(): array
+	{
+		return $this->namespaces;
+	}
+
+
+	/** @return static */
+	public function addUse(string $name, string $alias = null): self
+	{
+		$this->addNamespace('')->addUse($name, $alias);
+		return $this;
+	}
+
+
+	/**
+	 * Adds declare(strict_types=1) to output.
+	 * @return static
+	 */
+	public function setStrictTypes(bool $on = true): self
+	{
+		$this->strictTypes = $on;
+		return $this;
+	}
+
+
+	public function hasStrictTypes(): bool
+	{
+		return $this->strictTypes;
+	}
+
+
+	/** @deprecated  use hasStrictTypes() */
+	public function getStrictTypes(): bool
+	{
+		return $this->strictTypes;
+	}
+
+
+	public function __toString(): string
+	{
+		try {
+			return (new Printer)->printFile($this);
+		} catch (\Throwable $e) {
+			if (PHP_VERSION_ID >= 70400) {
+				throw $e;
+			}
+			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+			return '';
+		}
+	}
+}

+ 15 - 0
vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php

@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+
+class PhpLiteral extends Literal
+{
+}

+ 217 - 0
vendor/nette/php-generator/src/PhpGenerator/PhpNamespace.php

@@ -0,0 +1,217 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+use Nette\InvalidStateException;
+use Nette\Utils\Strings;
+
+
+/**
+ * Namespaced part of a PHP file.
+ *
+ * Generates:
+ * - namespace statement
+ * - variable amount of use statements
+ * - one or more class declarations
+ */
+final class PhpNamespace
+{
+	use Nette\SmartObject;
+
+	private const KEYWORDS = [
+		'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
+		'callable' => 1, 'iterable' => 1, 'void' => 1, 'self' => 1, 'parent' => 1, 'static' => 1,
+		'mixed' => 1, 'null' => 1, 'false' => 1, 'never' => 1,
+	];
+
+	/** @var string */
+	private $name;
+
+	/** @var bool */
+	private $bracketedSyntax = false;
+
+	/** @var string[] */
+	private $uses = [];
+
+	/** @var ClassType[] */
+	private $classes = [];
+
+
+	public function __construct(string $name)
+	{
+		if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
+			throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
+		}
+		$this->name = $name;
+	}
+
+
+	public function getName(): string
+	{
+		return $this->name;
+	}
+
+
+	/**
+	 * @return static
+	 * @internal
+	 */
+	public function setBracketedSyntax(bool $state = true): self
+	{
+		$this->bracketedSyntax = $state;
+		return $this;
+	}
+
+
+	public function hasBracketedSyntax(): bool
+	{
+		return $this->bracketedSyntax;
+	}
+
+
+	/** @deprecated  use hasBracketedSyntax() */
+	public function getBracketedSyntax(): bool
+	{
+		return $this->bracketedSyntax;
+	}
+
+
+	/**
+	 * @throws InvalidStateException
+	 * @return static
+	 */
+	public function addUse(string $name, string $alias = null, string &$aliasOut = null): self
+	{
+		$name = ltrim($name, '\\');
+		if ($alias === null && $this->name === Helpers::extractNamespace($name)) {
+			$alias = Helpers::extractShortName($name);
+		}
+		if ($alias === null) {
+			$path = explode('\\', $name);
+			$counter = null;
+			do {
+				if (empty($path)) {
+					$counter++;
+				} else {
+					$alias = array_pop($path) . $alias;
+				}
+			} while (isset($this->uses[$alias . $counter]) && $this->uses[$alias . $counter] !== $name);
+			$alias .= $counter;
+
+		} elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) {
+			throw new InvalidStateException(
+				"Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '{$name}'."
+			);
+		}
+
+		$aliasOut = $alias;
+		$this->uses[$alias] = $name;
+		asort($this->uses);
+		return $this;
+	}
+
+
+	/** @return string[] */
+	public function getUses(): array
+	{
+		return $this->uses;
+	}
+
+
+	public function unresolveType(string $type): string
+	{
+		return preg_replace_callback('~[^|&?]+~', function ($m) { return $this->unresolveName($m[0]); }, $type);
+	}
+
+
+	public function unresolveName(string $name): string
+	{
+		if (isset(self::KEYWORDS[strtolower($name)]) || $name === '') {
+			return $name;
+		}
+		$name = ltrim($name, '\\');
+		$res = null;
+		$lower = strtolower($name);
+		foreach ($this->uses as $alias => $original) {
+			if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) {
+				$short = $alias . substr($name, strlen($original));
+				if (!isset($res) || strlen($res) > strlen($short)) {
+					$res = $short;
+				}
+			}
+		}
+
+		if (!$res && Strings::startsWith($lower, strtolower($this->name) . '\\')) {
+			return substr($name, strlen($this->name) + 1);
+		} else {
+			return $res ?: ($this->name ? '\\' : '') . $name;
+		}
+	}
+
+
+	/** @return static */
+	public function add(ClassType $class): self
+	{
+		$name = $class->getName();
+		if ($name === null) {
+			throw new Nette\InvalidArgumentException('Class does not have a name.');
+		}
+		$this->addUse($this->name . '\\' . $name);
+		$this->classes[$name] = $class;
+		return $this;
+	}
+
+
+	public function addClass(string $name): ClassType
+	{
+		$this->add($class = new ClassType($name, $this));
+		return $class;
+	}
+
+
+	public function addInterface(string $name): ClassType
+	{
+		return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE);
+	}
+
+
+	public function addTrait(string $name): ClassType
+	{
+		return $this->addClass($name)->setType(ClassType::TYPE_TRAIT);
+	}
+
+
+	public function addEnum(string $name): ClassType
+	{
+		return $this->addClass($name)->setType(ClassType::TYPE_ENUM);
+	}
+
+
+	/** @return ClassType[] */
+	public function getClasses(): array
+	{
+		return $this->classes;
+	}
+
+
+	public function __toString(): string
+	{
+		try {
+			return (new Printer)->printNamespace($this);
+		} catch (\Throwable $e) {
+			if (PHP_VERSION_ID >= 70400) {
+				throw $e;
+			}
+			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+			return '';
+		}
+	}
+}

+ 366 - 0
vendor/nette/php-generator/src/PhpGenerator/Printer.php

@@ -0,0 +1,366 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+use Nette\Utils\Strings;
+
+
+/**
+ * Generates PHP code.
+ */
+class Printer
+{
+	use Nette\SmartObject;
+
+	/** @var string */
+	protected $indentation = "\t";
+
+	/** @var int */
+	protected $linesBetweenProperties = 0;
+
+	/** @var int */
+	protected $linesBetweenMethods = 2;
+
+	/** @var string */
+	protected $returnTypeColon = ': ';
+
+	/** @var bool */
+	private $resolveTypes = true;
+
+
+	public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null): string
+	{
+		$line = 'function '
+			. ($function->getReturnReference() ? '&' : '')
+			. $function->getName();
+		$returnType = $this->printReturnType($function, $namespace);
+
+		return Helpers::formatDocComment($function->getComment() . "\n")
+			. self::printAttributes($function->getAttributes(), $namespace)
+			. $line
+			. $this->printParameters($function, $namespace, strlen($line) + strlen($returnType) + 2) // 2 = parentheses
+			. $returnType
+			. "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n";
+	}
+
+
+	public function printClosure(Closure $closure): string
+	{
+		$uses = [];
+		foreach ($closure->getUses() as $param) {
+			$uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName();
+		}
+		$useStr = strlen($tmp = implode(', ', $uses)) > (new Dumper)->wrapLength && count($uses) > 1
+			? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n"
+			: $tmp;
+
+		return self::printAttributes($closure->getAttributes(), null, true)
+			. 'function '
+			. ($closure->getReturnReference() ? '&' : '')
+			. $this->printParameters($closure, null)
+			. ($uses ? " use ($useStr)" : '')
+			. $this->printReturnType($closure, null)
+			. " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}';
+	}
+
+
+	public function printArrowFunction(Closure $closure): string
+	{
+		foreach ($closure->getUses() as $use) {
+			if ($use->isReference()) {
+				throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.');
+			}
+		}
+
+		return self::printAttributes($closure->getAttributes(), null)
+			. 'fn'
+			. ($closure->getReturnReference() ? '&' : '')
+			. $this->printParameters($closure, null)
+			. $this->printReturnType($closure, null)
+			. ' => ' . trim($closure->getBody()) . ';';
+	}
+
+
+	public function printMethod(Method $method, PhpNamespace $namespace = null): string
+	{
+		$method->validate();
+		$line = ($method->isAbstract() ? 'abstract ' : '')
+			. ($method->isFinal() ? 'final ' : '')
+			. ($method->getVisibility() ? $method->getVisibility() . ' ' : '')
+			. ($method->isStatic() ? 'static ' : '')
+			. 'function '
+			. ($method->getReturnReference() ? '&' : '')
+			. $method->getName();
+		$returnType = $this->printReturnType($method, $namespace);
+
+		return Helpers::formatDocComment($method->getComment() . "\n")
+			. self::printAttributes($method->getAttributes(), $namespace)
+			. $line
+			. ($params = $this->printParameters($method, $namespace, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2)) // 2 = parentheses
+			. $returnType
+			. ($method->isAbstract() || $method->getBody() === null
+				? ";\n"
+				: (strpos($params, "\n") === false ? "\n" : ' ')
+					. "{\n"
+					. $this->indent(ltrim(rtrim($method->getBody()) . "\n"))
+					. "}\n");
+	}
+
+
+	public function printClass(ClassType $class, PhpNamespace $namespace = null): string
+	{
+		$class->validate();
+		$resolver = $this->resolveTypes && $namespace
+			? [$namespace, 'unresolveType']
+			: function ($s) { return $s; };
+
+		$traits = [];
+		foreach ($class->getTraitResolutions() as $trait => $resolutions) {
+			$traits[] = 'use ' . $resolver($trait)
+				. ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n");
+		}
+
+		$cases = [];
+		foreach ($class->getCases() as $case) {
+			$cases[] = Helpers::formatDocComment((string) $case->getComment())
+				. self::printAttributes($case->getAttributes(), $namespace)
+				. 'case ' . $case->getName()
+				. ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue()))
+				. ";\n";
+		}
+		$enumType = isset($case) && $case->getValue() !== null
+			? $this->returnTypeColon . Type::getType($case->getValue())
+			: '';
+
+		$consts = [];
+		foreach ($class->getConstants() as $const) {
+			$def = ($const->isFinal() ? 'final ' : '')
+				. ($const->getVisibility() ? $const->getVisibility() . ' ' : '')
+				. 'const ' . $const->getName() . ' = ';
+
+			$consts[] = Helpers::formatDocComment((string) $const->getComment())
+				. self::printAttributes($const->getAttributes(), $namespace)
+				. $def
+				. $this->dump($const->getValue(), strlen($def)) . ";\n";
+		}
+
+		$properties = [];
+		foreach ($class->getProperties() as $property) {
+			$type = $property->getType();
+			$def = (($property->getVisibility() ?: 'public')
+				. ($property->isStatic() ? ' static' : '')
+				. ($property->isReadOnly() && $type ? ' readonly' : '')
+				. ' '
+				. ltrim($this->printType($type, $property->isNullable(), $namespace) . ' ')
+				. '$' . $property->getName());
+
+			$properties[] = Helpers::formatDocComment((string) $property->getComment())
+				. self::printAttributes($property->getAttributes(), $namespace)
+				. $def
+				. ($property->getValue() === null && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '
+				. ";\n";
+		}
+
+		$methods = [];
+		foreach ($class->getMethods() as $method) {
+			$methods[] = $this->printMethod($method, $namespace);
+		}
+
+		$members = array_filter([
+			implode('', $traits),
+			$this->joinProperties($cases),
+			$this->joinProperties($consts),
+			$this->joinProperties($properties),
+			($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '')
+			. implode(str_repeat("\n", $this->linesBetweenMethods), $methods),
+		]);
+
+		return Strings::normalize(
+			Helpers::formatDocComment($class->getComment() . "\n")
+			. self::printAttributes($class->getAttributes(), $namespace)
+			. ($class->isAbstract() ? 'abstract ' : '')
+			. ($class->isFinal() ? 'final ' : '')
+			. ($class->getName() ? $class->getType() . ' ' . $class->getName() . $enumType . ' ' : '')
+			. ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '')
+			. ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '')
+			. ($class->getName() ? "\n" : '') . "{\n"
+			. ($members ? $this->indent(implode("\n", $members)) : '')
+			. '}'
+		) . ($class->getName() ? "\n" : '');
+	}
+
+
+	public function printNamespace(PhpNamespace $namespace): string
+	{
+		$name = $namespace->getName();
+		$uses = $this->printUses($namespace);
+
+		$classes = [];
+		foreach ($namespace->getClasses() as $class) {
+			$classes[] = $this->printClass($class, $namespace);
+		}
+
+		$body = ($uses ? $uses . "\n\n" : '')
+			. implode("\n", $classes);
+
+		if ($namespace->hasBracketedSyntax()) {
+			return 'namespace' . ($name ? " $name" : '') . "\n{\n"
+				. $this->indent($body)
+				. "}\n";
+
+		} else {
+			return ($name ? "namespace $name;\n\n" : '')
+				. $body;
+		}
+	}
+
+
+	public function printFile(PhpFile $file): string
+	{
+		$namespaces = [];
+		foreach ($file->getNamespaces() as $namespace) {
+			$namespaces[] = $this->printNamespace($namespace);
+		}
+
+		return Strings::normalize(
+			"<?php\n"
+			. ($file->getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '')
+			. "\n"
+			. ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '')
+			. implode("\n\n", $namespaces)
+		) . "\n";
+	}
+
+
+	/** @return static */
+	public function setTypeResolving(bool $state = true): self
+	{
+		$this->resolveTypes = $state;
+		return $this;
+	}
+
+
+	protected function indent(string $s): string
+	{
+		$s = str_replace("\t", $this->indentation, $s);
+		return Strings::indent($s, 1, $this->indentation);
+	}
+
+
+	protected function dump($var, int $column = 0): string
+	{
+		return (new Dumper)->dump($var, $column);
+	}
+
+
+	protected function printUses(PhpNamespace $namespace): string
+	{
+		$name = $namespace->getName();
+		$uses = [];
+		foreach ($namespace->getUses() as $alias => $original) {
+			if ($original !== ($name ? $name . '\\' . $alias : $alias)) {
+				$uses[] = $alias === $original || substr($original, -(strlen($alias) + 1)) === '\\' . $alias
+					? "use $original;"
+					: "use $original as $alias;";
+			}
+		}
+		return implode("\n", $uses);
+	}
+
+
+	/**
+	 * @param Closure|GlobalFunction|Method  $function
+	 */
+	public function printParameters($function, PhpNamespace $namespace = null, int $column = 0): string
+	{
+		$params = [];
+		$list = $function->getParameters();
+		$special = false;
+
+		foreach ($list as $param) {
+			$variadic = $function->isVariadic() && $param === end($list);
+			$type = $param->getType();
+			$promoted = $param instanceof PromotedParameter ? $param : null;
+			$params[] =
+				($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '')
+				. ($attrs = self::printAttributes($param->getAttributes(), $namespace, true))
+				. ($promoted ?
+					($promoted->getVisibility() ?: 'public')
+					. ($promoted->isReadOnly() && $type ? ' readonly' : '')
+					. ' ' : '')
+				. ltrim($this->printType($type, $param->isNullable(), $namespace) . ' ')
+				. ($param->isReference() ? '&' : '')
+				. ($variadic ? '...' : '')
+				. '$' . $param->getName()
+				. ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : '');
+
+			$special = $special || $promoted || $attrs;
+		}
+
+		$line = implode(', ', $params);
+
+		return count($params) > 1 && ($special || strlen($line) + $column > (new Dumper)->wrapLength)
+			? "(\n" . $this->indent(implode(",\n", $params)) . ($special ? ',' : '') . "\n)"
+			: "($line)";
+	}
+
+
+	public function printType(?string $type, bool $nullable = false, PhpNamespace $namespace = null): string
+	{
+		if ($type === null) {
+			return '';
+		}
+		if ($this->resolveTypes && $namespace) {
+			$type = $namespace->unresolveType($type);
+		}
+		if ($nullable && strcasecmp($type, 'mixed')) {
+			$type = strpos($type, '|') === false
+				? '?' . $type
+				: $type . '|null';
+		}
+		return $type;
+	}
+
+
+	/**
+	 * @param Closure|GlobalFunction|Method  $function
+	 */
+	private function printReturnType($function, ?PhpNamespace $namespace): string
+	{
+		return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable(), $namespace))
+			? $this->returnTypeColon . $tmp
+			: '';
+	}
+
+
+	private function printAttributes(array $attrs, ?PhpNamespace $namespace, bool $inline = false): string
+	{
+		if (!$attrs) {
+			return '';
+		}
+		$items = [];
+		foreach ($attrs as $attr) {
+			$args = (new Dumper)->format('...?:', $attr->getArguments());
+			$items[] = $this->printType($attr->getName(), false, $namespace) . ($args ? "($args)" : '');
+		}
+		return $inline
+			? '#[' . implode(', ', $items) . '] '
+			: '#[' . implode("]\n#[", $items) . "]\n";
+	}
+
+
+	private function joinProperties(array $props)
+	{
+		return $this->linesBetweenProperties
+			? implode(str_repeat("\n", $this->linesBetweenProperties), $props)
+			: preg_replace('#^(\w.*\n)\n(?=\w.*;)#m', '$1', implode("\n", $props));
+	}
+}

+ 37 - 0
vendor/nette/php-generator/src/PhpGenerator/PromotedParameter.php

@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+
+/**
+ * Promoted parameter in constructor.
+ */
+final class PromotedParameter extends Parameter
+{
+	use Traits\VisibilityAware;
+	use Traits\CommentAware;
+
+	/** @var bool */
+	private $readOnly = false;
+
+
+	/** @return static */
+	public function setReadOnly(bool $state = true): self
+	{
+		$this->readOnly = $state;
+		return $this;
+	}
+
+
+	public function isReadOnly(): bool
+	{
+		return $this->readOnly;
+	}
+}

+ 134 - 0
vendor/nette/php-generator/src/PhpGenerator/Property.php

@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+use Nette;
+
+
+/**
+ * Class property description.
+ *
+ * @property mixed $value
+ */
+final class Property
+{
+	use Nette\SmartObject;
+	use Traits\NameAware;
+	use Traits\VisibilityAware;
+	use Traits\CommentAware;
+	use Traits\AttributeAware;
+
+	/** @var mixed */
+	private $value;
+
+	/** @var bool */
+	private $static = false;
+
+	/** @var string|null */
+	private $type;
+
+	/** @var bool */
+	private $nullable = false;
+
+	/** @var bool */
+	private $initialized = false;
+
+	/** @var bool */
+	private $readOnly = false;
+
+
+	/** @return static */
+	public function setValue($val): self
+	{
+		$this->value = $val;
+		$this->initialized = true;
+		return $this;
+	}
+
+
+	public function &getValue()
+	{
+		return $this->value;
+	}
+
+
+	/** @return static */
+	public function setStatic(bool $state = true): self
+	{
+		$this->static = $state;
+		return $this;
+	}
+
+
+	public function isStatic(): bool
+	{
+		return $this->static;
+	}
+
+
+	/** @return static */
+	public function setType(?string $type): self
+	{
+		if ($type && $type[0] === '?') {
+			$type = substr($type, 1);
+			$this->nullable = true;
+		}
+		$this->type = $type;
+		return $this;
+	}
+
+
+	public function getType(): ?string
+	{
+		return $this->type;
+	}
+
+
+	/** @return static */
+	public function setNullable(bool $state = true): self
+	{
+		$this->nullable = $state;
+		return $this;
+	}
+
+
+	public function isNullable(): bool
+	{
+		return $this->nullable;
+	}
+
+
+	/** @return static */
+	public function setInitialized(bool $state = true): self
+	{
+		$this->initialized = $state;
+		return $this;
+	}
+
+
+	public function isInitialized(): bool
+	{
+		return $this->initialized || $this->value !== null;
+	}
+
+
+	/** @return static */
+	public function setReadOnly(bool $state = true): self
+	{
+		$this->readOnly = $state;
+		return $this;
+	}
+
+
+	public function isReadOnly(): bool
+	{
+		return $this->readOnly;
+	}
+}

+ 23 - 0
vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php

@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+
+/**
+ * Generates PHP code compatible with PSR-2 and PSR-12.
+ */
+final class PsrPrinter extends Printer
+{
+	/** @var string */
+	protected $indentation = '    ';
+
+	/** @var int */
+	protected $linesBetweenMethods = 1;
+}

+ 49 - 0
vendor/nette/php-generator/src/PhpGenerator/Traits/AttributeAware.php

@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator\Traits;
+
+use Nette\PhpGenerator\Attribute;
+
+
+/**
+ * @internal
+ */
+trait AttributeAware
+{
+	/** @var Attribute[] */
+	private $attributes = [];
+
+
+	/** @return static */
+	public function addAttribute(string $name, array $args = []): self
+	{
+		$this->attributes[] = new Attribute($name, $args);
+		return $this;
+	}
+
+
+	/**
+	 * @param  Attribute[]  $attrs
+	 * @return static
+	 */
+	public function setAttributes(array $attrs): self
+	{
+		(function (Attribute ...$attrs) {})(...$attrs);
+		$this->attributes = $attrs;
+		return $this;
+	}
+
+
+	/** @return Attribute[] */
+	public function getAttributes(): array
+	{
+		return $this->attributes;
+	}
+}

+ 42 - 0
vendor/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php

@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator\Traits;
+
+
+/**
+ * @internal
+ */
+trait CommentAware
+{
+	/** @var string|null */
+	private $comment;
+
+
+	/** @return static */
+	public function setComment(?string $val): self
+	{
+		$this->comment = $val;
+		return $this;
+	}
+
+
+	public function getComment(): ?string
+	{
+		return $this->comment;
+	}
+
+
+	/** @return static */
+	public function addComment(string $val): self
+	{
+		$this->comment .= $this->comment ? "\n$val" : $val;
+		return $this;
+	}
+}

+ 186 - 0
vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php

@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator\Traits;
+
+use Nette;
+use Nette\PhpGenerator\Dumper;
+use Nette\PhpGenerator\Parameter;
+
+
+/**
+ * @internal
+ */
+trait FunctionLike
+{
+	/** @var string */
+	private $body = '';
+
+	/** @var Parameter[] */
+	private $parameters = [];
+
+	/** @var bool */
+	private $variadic = false;
+
+	/** @var string|null */
+	private $returnType;
+
+	/** @var bool */
+	private $returnReference = false;
+
+	/** @var bool */
+	private $returnNullable = false;
+
+
+	/** @return static */
+	public function setBody(string $code, array $args = null): self
+	{
+		$this->body = $args === null
+			? $code
+			: (new Dumper)->format($code, ...$args);
+		return $this;
+	}
+
+
+	public function getBody(): string
+	{
+		return $this->body;
+	}
+
+
+	/** @return static */
+	public function addBody(string $code, array $args = null): self
+	{
+		$this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n";
+		return $this;
+	}
+
+
+	/**
+	 * @param  Parameter[]  $val
+	 * @return static
+	 */
+	public function setParameters(array $val): self
+	{
+		$this->parameters = [];
+		foreach ($val as $v) {
+			if (!$v instanceof Parameter) {
+				throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Parameter[].');
+			}
+			$this->parameters[$v->getName()] = $v;
+		}
+		return $this;
+	}
+
+
+	/** @return Parameter[] */
+	public function getParameters(): array
+	{
+		return $this->parameters;
+	}
+
+
+	/**
+	 * @param  string  $name without $
+	 */
+	public function addParameter(string $name, $defaultValue = null): Parameter
+	{
+		$param = new Parameter($name);
+		if (func_num_args() > 1) {
+			$param->setDefaultValue($defaultValue);
+		}
+		return $this->parameters[$name] = $param;
+	}
+
+
+	/**
+	 * @param  string  $name without $
+	 * @return static
+	 */
+	public function removeParameter(string $name): self
+	{
+		unset($this->parameters[$name]);
+		return $this;
+	}
+
+
+	/** @return static */
+	public function setVariadic(bool $state = true): self
+	{
+		$this->variadic = $state;
+		return $this;
+	}
+
+
+	public function isVariadic(): bool
+	{
+		return $this->variadic;
+	}
+
+
+	/** @return static */
+	public function setReturnType(?string $type): self
+	{
+		if ($type && $type[0] === '?') {
+			$type = substr($type, 1);
+			$this->returnNullable = true;
+		}
+		$this->returnType = $type;
+		return $this;
+	}
+
+
+	public function getReturnType(): ?string
+	{
+		return $this->returnType;
+	}
+
+
+	/** @return static */
+	public function setReturnReference(bool $state = true): self
+	{
+		$this->returnReference = $state;
+		return $this;
+	}
+
+
+	public function getReturnReference(): bool
+	{
+		return $this->returnReference;
+	}
+
+
+	/** @return static */
+	public function setReturnNullable(bool $state = true): self
+	{
+		$this->returnNullable = $state;
+		return $this;
+	}
+
+
+	public function isReturnNullable(): bool
+	{
+		return $this->returnNullable;
+	}
+
+
+	/** @deprecated  use isReturnNullable() */
+	public function getReturnNullable(): bool
+	{
+		return $this->returnNullable;
+	}
+
+
+	/** @deprecated */
+	public function setNamespace(Nette\PhpGenerator\PhpNamespace $val = null): self
+	{
+		trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
+		return $this;
+	}
+}

+ 49 - 0
vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php

@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator\Traits;
+
+use Nette;
+
+
+/**
+ * @internal
+ */
+trait NameAware
+{
+	/** @var string */
+	private $name;
+
+
+	public function __construct(string $name)
+	{
+		if (!Nette\PhpGenerator\Helpers::isIdentifier($name)) {
+			throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
+		}
+		$this->name = $name;
+	}
+
+
+	public function getName(): string
+	{
+		return $this->name;
+	}
+
+
+	/**
+	 * Returns clone with a different name.
+	 * @return static
+	 */
+	public function cloneWithName(string $name): self
+	{
+		$dolly = clone $this;
+		$dolly->__construct($name);
+		return $dolly;
+	}
+}

+ 85 - 0
vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php

@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator\Traits;
+
+use Nette;
+use Nette\PhpGenerator\ClassType;
+
+
+/**
+ * @internal
+ */
+trait VisibilityAware
+{
+	/** @var string|null  public|protected|private */
+	private $visibility;
+
+
+	/**
+	 * @param  string|null  $val  public|protected|private
+	 * @return static
+	 */
+	public function setVisibility(?string $val): self
+	{
+		if (!in_array($val, [ClassType::VISIBILITY_PUBLIC, ClassType::VISIBILITY_PROTECTED, ClassType::VISIBILITY_PRIVATE, null], true)) {
+			throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
+		}
+		$this->visibility = $val;
+		return $this;
+	}
+
+
+	public function getVisibility(): ?string
+	{
+		return $this->visibility;
+	}
+
+
+	/** @return static */
+	public function setPublic(): self
+	{
+		$this->visibility = ClassType::VISIBILITY_PUBLIC;
+		return $this;
+	}
+
+
+	public function isPublic(): bool
+	{
+		return $this->visibility === ClassType::VISIBILITY_PUBLIC || $this->visibility === null;
+	}
+
+
+	/** @return static */
+	public function setProtected(): self
+	{
+		$this->visibility = ClassType::VISIBILITY_PROTECTED;
+		return $this;
+	}
+
+
+	public function isProtected(): bool
+	{
+		return $this->visibility === ClassType::VISIBILITY_PROTECTED;
+	}
+
+
+	/** @return static */
+	public function setPrivate(): self
+	{
+		$this->visibility = ClassType::VISIBILITY_PRIVATE;
+		return $this;
+	}
+
+
+	public function isPrivate(): bool
+	{
+		return $this->visibility === ClassType::VISIBILITY_PRIVATE;
+	}
+}

+ 73 - 0
vendor/nette/php-generator/src/PhpGenerator/Type.php

@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\PhpGenerator;
+
+
+/**
+ * PHP return, property and parameter types.
+ */
+class Type
+{
+	public const
+		STRING = 'string',
+		INT = 'int',
+		FLOAT = 'float',
+		BOOL = 'bool',
+		ARRAY = 'array',
+		OBJECT = 'object',
+		CALLABLE = 'callable',
+		ITERABLE = 'iterable',
+		VOID = 'void',
+		NEVER = 'never',
+		MIXED = 'mixed',
+		FALSE = 'false',
+		NULL = 'null',
+		SELF = 'self',
+		PARENT = 'parent',
+		STATIC = 'static';
+
+
+	public static function nullable(string $type, bool $state = true): string
+	{
+		return ($state ? '?' : '') . ltrim($type, '?');
+	}
+
+
+	public static function union(string ...$types): string
+	{
+		return implode('|', $types);
+	}
+
+
+	public static function intersection(string ...$types): string
+	{
+		return implode('&', $types);
+	}
+
+
+	public static function getType($value): ?string
+	{
+		if (is_object($value)) {
+			return get_class($value);
+		} elseif (is_int($value)) {
+			return self::INT;
+		} elseif (is_float($value)) {
+			return self::FLOAT;
+		} elseif (is_string($value)) {
+			return self::STRING;
+		} elseif (is_bool($value)) {
+			return self::BOOL;
+		} elseif (is_array($value)) {
+			return self::ARRAY;
+		} else {
+			return null;
+		}
+	}
+}

+ 19 - 0
vendor/nette/utils/.phpstorm.meta.php

@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace PHPSTORM_META;
+
+override(\Nette\Utils\Arrays::get(0), elementType(0));
+override(\Nette\Utils\Arrays::getRef(0), elementType(0));
+override(\Nette\Utils\Arrays::grep(0), type(0));
+override(\Nette\Utils\Arrays::toObject(0), type(1));
+
+expectedArguments(\Nette\Utils\Arrays::grep(), 2, PREG_GREP_INVERT);
+expectedArguments(\Nette\Utils\Image::resize(), 2, \Nette\Utils\Image::SHRINK_ONLY, \Nette\Utils\Image::STRETCH, \Nette\Utils\Image::FIT, \Nette\Utils\Image::FILL, \Nette\Utils\Image::EXACT);
+expectedArguments(\Nette\Utils\Image::calculateSize(), 4, \Nette\Utils\Image::SHRINK_ONLY, \Nette\Utils\Image::STRETCH, \Nette\Utils\Image::FIT, \Nette\Utils\Image::FILL, \Nette\Utils\Image::EXACT);
+expectedArguments(\Nette\Utils\Json::encode(), 1, \Nette\Utils\Json::PRETTY);
+expectedArguments(\Nette\Utils\Json::decode(), 1, \Nette\Utils\Json::FORCE_ARRAY);
+expectedArguments(\Nette\Utils\Strings::split(), 2, \PREG_SPLIT_NO_EMPTY);
+expectedArguments(\Nette\Utils\Strings::match(), 2, \PREG_OFFSET_CAPTURE);
+expectedArguments(\Nette\Utils\Strings::matchAll(), 2, \PREG_OFFSET_CAPTURE, \PREG_SET_ORDER);

+ 50 - 0
vendor/nette/utils/composer.json

@@ -0,0 +1,50 @@
+{
+	"name": "nette/utils",
+	"description": "🛠  Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
+	"keywords": ["nette", "images", "json", "password", "validation", "utility", "string", "array", "core", "slugify", "utf-8", "unicode", "paginator", "datetime"],
+	"homepage": "https://nette.org",
+	"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
+	"authors": [
+		{
+			"name": "David Grudl",
+			"homepage": "https://davidgrudl.com"
+		},
+		{
+			"name": "Nette Community",
+			"homepage": "https://nette.org/contributors"
+		}
+	],
+	"require": {
+		"php": ">=7.2 <8.1"
+	},
+	"require-dev": {
+		"nette/tester": "~2.0",
+		"tracy/tracy": "^2.3",
+		"phpstan/phpstan": "^0.12"
+	},
+	"conflict": {
+		"nette/di": "<3.0.6"
+	},
+	"suggest": {
+		"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
+		"ext-json": "to use Nette\\Utils\\Json",
+		"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
+		"ext-mbstring": "to use Strings::lower() etc...",
+		"ext-xml": "to use Strings::length() etc. when mbstring is not available",
+		"ext-gd": "to use Image",
+		"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
+	},
+	"autoload": {
+		"classmap": ["src/"]
+	},
+	"minimum-stability": "dev",
+	"scripts": {
+		"phpstan": "phpstan analyse",
+		"tester": "tester tests -s"
+	},
+	"extra": {
+		"branch-alias": {
+			"dev-master": "3.2-dev"
+		}
+	}
+}

+ 33 - 0
vendor/nette/utils/contributing.md

@@ -0,0 +1,33 @@
+How to contribute & use the issue tracker
+=========================================
+
+Nette welcomes your contributions. There are several ways to help out:
+
+* Create an issue on GitHub, if you have found a bug
+* Write test cases for open bug issues
+* Write fixes for open bug/feature issues, preferably with test cases included
+* Contribute to the [documentation](https://nette.org/en/writing)
+
+Issues
+------
+
+Please **do not use the issue tracker to ask questions**. We will be happy to help you
+on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
+
+A good bug report shouldn't leave others needing to chase you up for more
+information. Please try to be as detailed as possible in your report.
+
+**Feature requests** are welcome. But take a moment to find out whether your idea
+fits with the scope and aims of the project. It's up to *you* to make a strong
+case to convince the project's developers of the merits of this feature.
+
+Contributing
+------------
+
+If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
+
+The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
+
+Please do not fix whitespace, format code, or make a purely cosmetic patch.
+
+Thanks! :heart:

+ 60 - 0
vendor/nette/utils/license.md

@@ -0,0 +1,60 @@
+Licenses
+========
+
+Good news! You may use Nette Framework under the terms of either
+the New BSD License or the GNU General Public License (GPL) version 2 or 3.
+
+The BSD License is recommended for most projects. It is easy to understand and it
+places almost no restrictions on what you can do with the framework. If the GPL
+fits better to your project, you can use the framework under this license.
+
+You don't have to notify anyone which license you are using. You can freely
+use Nette Framework in commercial projects as long as the copyright header
+remains intact.
+
+Please be advised that the name "Nette Framework" is a protected trademark and its
+usage has some limitations. So please do not use word "Nette" in the name of your
+project or top-level domain, and choose a name that stands on its own merits.
+If your stuff is good, it will not take long to establish a reputation for yourselves.
+
+
+New BSD License
+---------------
+
+Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+	* Redistributions of source code must retain the above copyright notice,
+	this list of conditions and the following disclaimer.
+
+	* Redistributions in binary form must reproduce the above copyright notice,
+	this list of conditions and the following disclaimer in the documentation
+	and/or other materials provided with the distribution.
+
+	* Neither the name of "Nette Framework" nor the names of its contributors
+	may be used to endorse or promote products derived from this software
+	without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as is" and
+any express or implied warranties, including, but not limited to, the implied
+warranties of merchantability and fitness for a particular purpose are
+disclaimed. In no event shall the copyright owner or contributors be liable for
+any direct, indirect, incidental, special, exemplary, or consequential damages
+(including, but not limited to, procurement of substitute goods or services;
+loss of use, data, or profits; or business interruption) however caused and on
+any theory of liability, whether in contract, strict liability, or tort
+(including negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+
+GNU General Public License
+--------------------------
+
+GPL licenses are very very long, so instead of including them here we offer
+you URLs with full text:
+
+- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
+- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)

+ 53 - 0
vendor/nette/utils/readme.md

@@ -0,0 +1,53 @@
+Nette Utility Classes
+=====================
+
+[![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils)
+[![Tests](https://github.com/nette/utils/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/utils/actions)
+[![Coverage Status](https://coveralls.io/repos/github/nette/utils/badge.svg?branch=master)](https://coveralls.io/github/nette/utils?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/nette/utils/v/stable)](https://github.com/nette/utils/releases)
+[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/utils/blob/master/license.md)
+
+
+Introduction
+------------
+
+In package nette/utils you will find a set of [useful classes](https://doc.nette.org/utils) for everyday use:
+
+- [Arrays](https://doc.nette.org/arrays) - manipulate arrays
+- [Callback](https://doc.nette.org/callback) - PHP callbacks
+- [Date and Time](https://doc.nette.org/datetime) - modify times and dates
+- [Filesystem](https://doc.nette.org/filesystem) - copying, renaming, …
+- [Helper Functions](https://doc.nette.org/helpers)
+- [HTML elements](https://doc.nette.org/html-elements) - generate HTML
+- [Images](https://doc.nette.org/images) - crop, resize, rotate images
+- [JSON](https://doc.nette.org/json) - encoding and decoding
+- [Generating Random Strings](https://doc.nette.org/random)
+- [Paginator](https://doc.nette.org/paginator) - pagination math
+- [PHP Reflection](https://doc.nette.org/reflection)
+- [Strings](https://doc.nette.org/strings) - useful text functions
+- [SmartObject](https://doc.nette.org/smartobject) - PHP object enhancements
+- [Validation](https://doc.nette.org/validators) - validate inputs
+
+
+Installation
+------------
+
+The recommended way to install is via Composer:
+
+```
+composer require nette/utils
+```
+
+- Nette Utils 3.2 is compatible with PHP 7.2 to 8.0
+- Nette Utils 3.1 is compatible with PHP 7.1 to 8.0
+- Nette Utils 3.0 is compatible with PHP 7.1 to 8.0
+- Nette Utils 2.5 is compatible with PHP 5.6 to 8.0
+
+[Support Me](https://github.com/sponsors/dg)
+--------------------------------------------
+
+Do you like Nette Utils? Are you looking forward to the new features?
+
+[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
+
+Thank you!

+ 22 - 0
vendor/nette/utils/src/HtmlStringable.php

@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette;
+
+
+interface HtmlStringable
+{
+	/**
+	 * Returns string in HTML format
+	 */
+	function __toString(): string;
+}
+
+
+interface_exists(Utils\IHtmlString::class);

+ 166 - 0
vendor/nette/utils/src/Iterators/CachingIterator.php

@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Iterators;
+
+use Nette;
+
+
+/**
+ * Smarter caching iterator.
+ *
+ * @property-read bool $first
+ * @property-read bool $last
+ * @property-read bool $empty
+ * @property-read bool $odd
+ * @property-read bool $even
+ * @property-read int $counter
+ * @property-read mixed $nextKey
+ * @property-read mixed $nextValue
+ */
+class CachingIterator extends \CachingIterator implements \Countable
+{
+	use Nette\SmartObject;
+
+	/** @var int */
+	private $counter = 0;
+
+
+	public function __construct($iterator)
+	{
+		if (is_array($iterator) || $iterator instanceof \stdClass) {
+			$iterator = new \ArrayIterator($iterator);
+
+		} elseif ($iterator instanceof \IteratorAggregate) {
+			do {
+				$iterator = $iterator->getIterator();
+			} while ($iterator instanceof \IteratorAggregate);
+			assert($iterator instanceof \Iterator);
+
+		} elseif ($iterator instanceof \Iterator) {
+		} elseif ($iterator instanceof \Traversable) {
+			$iterator = new \IteratorIterator($iterator);
+		} else {
+			throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, is_object($iterator) ? get_class($iterator) : gettype($iterator)));
+		}
+
+		parent::__construct($iterator, 0);
+	}
+
+
+	/**
+	 * Is the current element the first one?
+	 */
+	public function isFirst(int $gridWidth = null): bool
+	{
+		return $this->counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0);
+	}
+
+
+	/**
+	 * Is the current element the last one?
+	 */
+	public function isLast(int $gridWidth = null): bool
+	{
+		return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0);
+	}
+
+
+	/**
+	 * Is the iterator empty?
+	 */
+	public function isEmpty(): bool
+	{
+		return $this->counter === 0;
+	}
+
+
+	/**
+	 * Is the counter odd?
+	 */
+	public function isOdd(): bool
+	{
+		return $this->counter % 2 === 1;
+	}
+
+
+	/**
+	 * Is the counter even?
+	 */
+	public function isEven(): bool
+	{
+		return $this->counter % 2 === 0;
+	}
+
+
+	/**
+	 * Returns the counter.
+	 */
+	public function getCounter(): int
+	{
+		return $this->counter;
+	}
+
+
+	/**
+	 * Returns the count of elements.
+	 */
+	public function count(): int
+	{
+		$inner = $this->getInnerIterator();
+		if ($inner instanceof \Countable) {
+			return $inner->count();
+
+		} else {
+			throw new Nette\NotSupportedException('Iterator is not countable.');
+		}
+	}
+
+
+	/**
+	 * Forwards to the next element.
+	 */
+	public function next(): void
+	{
+		parent::next();
+		if (parent::valid()) {
+			$this->counter++;
+		}
+	}
+
+
+	/**
+	 * Rewinds the Iterator.
+	 */
+	public function rewind(): void
+	{
+		parent::rewind();
+		$this->counter = parent::valid() ? 1 : 0;
+	}
+
+
+	/**
+	 * Returns the next key.
+	 * @return mixed
+	 */
+	public function getNextKey()
+	{
+		return $this->getInnerIterator()->key();
+	}
+
+
+	/**
+	 * Returns the next element.
+	 * @return mixed
+	 */
+	public function getNextValue()
+	{
+		return $this->getInnerIterator()->current();
+	}
+}

+ 34 - 0
vendor/nette/utils/src/Iterators/Mapper.php

@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Iterators;
+
+
+
+/**
+ * Applies the callback to the elements of the inner iterator.
+ */
+class Mapper extends \IteratorIterator
+{
+	/** @var callable */
+	private $callback;
+
+
+	public function __construct(\Traversable $iterator, callable $callback)
+	{
+		parent::__construct($iterator);
+		$this->callback = $callback;
+	}
+
+
+	public function current()
+	{
+		return ($this->callback)(parent::current(), parent::key());
+	}
+}

+ 122 - 0
vendor/nette/utils/src/SmartObject.php

@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette;
+
+use Nette\Utils\ObjectHelpers;
+
+
+/**
+ * Strict class for better experience.
+ * - 'did you mean' hints
+ * - access to undeclared members throws exceptions
+ * - support for @property annotations
+ * - support for calling event handlers stored in $onEvent via onEvent()
+ */
+trait SmartObject
+{
+	/**
+	 * @throws MemberAccessException
+	 */
+	public function __call(string $name, array $args)
+	{
+		$class = static::class;
+
+		if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers
+			$handlers = $this->$name ?? null;
+			if (is_iterable($handlers)) {
+				foreach ($handlers as $handler) {
+					$handler(...$args);
+				}
+			} elseif ($handlers !== null) {
+				throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($handlers) . ' given.');
+			}
+
+		} else {
+			ObjectHelpers::strictCall($class, $name);
+		}
+	}
+
+
+	/**
+	 * @throws MemberAccessException
+	 */
+	public static function __callStatic(string $name, array $args)
+	{
+		ObjectHelpers::strictStaticCall(static::class, $name);
+	}
+
+
+	/**
+	 * @return mixed
+	 * @throws MemberAccessException if the property is not defined.
+	 */
+	public function &__get(string $name)
+	{
+		$class = static::class;
+
+		if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter
+			if (!($prop & 0b0001)) {
+				throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
+			}
+			$m = ($prop & 0b0010 ? 'get' : 'is') . $name;
+			if ($prop & 0b0100) { // return by reference
+				return $this->$m();
+			} else {
+				$val = $this->$m();
+				return $val;
+			}
+		} else {
+			ObjectHelpers::strictGet($class, $name);
+		}
+	}
+
+
+	/**
+	 * @param  mixed  $value
+	 * @return void
+	 * @throws MemberAccessException if the property is not defined or is read-only
+	 */
+	public function __set(string $name, $value)
+	{
+		$class = static::class;
+
+		if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property
+			$this->$name = $value;
+
+		} elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter
+			if (!($prop & 0b1000)) {
+				throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
+			}
+			$this->{'set' . $name}($value);
+
+		} else {
+			ObjectHelpers::strictSet($class, $name);
+		}
+	}
+
+
+	/**
+	 * @return void
+	 * @throws MemberAccessException
+	 */
+	public function __unset(string $name)
+	{
+		$class = static::class;
+		if (!ObjectHelpers::hasProperty($class, $name)) {
+			throw new MemberAccessException("Cannot unset the property $class::\$$name.");
+		}
+	}
+
+
+	public function __isset(string $name): bool
+	{
+		return isset(ObjectHelpers::getMagicProperties(static::class)[$name]);
+	}
+}

+ 34 - 0
vendor/nette/utils/src/StaticClass.php

@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette;
+
+
+/**
+ * Static class.
+ */
+trait StaticClass
+{
+	/** @throws \Error */
+	final public function __construct()
+	{
+		throw new \Error('Class ' . static::class . ' is static and cannot be instantiated.');
+	}
+
+
+	/**
+	 * Call to undefined static method.
+	 * @return void
+	 * @throws MemberAccessException
+	 */
+	public static function __callStatic(string $name, array $args)
+	{
+		Utils\ObjectHelpers::strictStaticCall(static::class, $name);
+	}
+}

+ 27 - 0
vendor/nette/utils/src/Translator.php

@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Localization;
+
+
+/**
+ * Translator adapter.
+ */
+interface Translator
+{
+	/**
+	 * Translates the given string.
+	 * @param  mixed  $message
+	 * @param  mixed  ...$parameters
+	 */
+	function translate($message, ...$parameters): string;
+}
+
+
+interface_exists(ITranslator::class);

+ 97 - 0
vendor/nette/utils/src/Utils/ArrayHash.php

@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Provides objects to work as array.
+ */
+class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate
+{
+	/**
+	 * Transforms array to ArrayHash.
+	 * @return static
+	 */
+	public static function from(array $array, bool $recursive = true)
+	{
+		$obj = new static;
+		foreach ($array as $key => $value) {
+			$obj->$key = $recursive && is_array($value)
+				? static::from($value, true)
+				: $value;
+		}
+		return $obj;
+	}
+
+
+	/**
+	 * Returns an iterator over all items.
+	 */
+	public function getIterator(): \RecursiveArrayIterator
+	{
+		return new \RecursiveArrayIterator((array) $this);
+	}
+
+
+	/**
+	 * Returns items count.
+	 */
+	public function count(): int
+	{
+		return count((array) $this);
+	}
+
+
+	/**
+	 * Replaces or appends a item.
+	 * @param  string|int  $key
+	 * @param  mixed  $value
+	 */
+	public function offsetSet($key, $value): void
+	{
+		if (!is_scalar($key)) { // prevents null
+			throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', gettype($key)));
+		}
+		$this->$key = $value;
+	}
+
+
+	/**
+	 * Returns a item.
+	 * @param  string|int  $key
+	 * @return mixed
+	 */
+	public function offsetGet($key)
+	{
+		return $this->$key;
+	}
+
+
+	/**
+	 * Determines whether a item exists.
+	 * @param  string|int  $key
+	 */
+	public function offsetExists($key): bool
+	{
+		return isset($this->$key);
+	}
+
+
+	/**
+	 * Removes the element from this list.
+	 * @param  string|int  $key
+	 */
+	public function offsetUnset($key): void
+	{
+		unset($this->$key);
+	}
+}

+ 113 - 0
vendor/nette/utils/src/Utils/ArrayList.php

@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Provides the base class for a generic list (items can be accessed by index).
+ */
+class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
+{
+	use Nette\SmartObject;
+
+	/** @var mixed[] */
+	private $list = [];
+
+
+	/**
+	 * Returns an iterator over all items.
+	 */
+	public function getIterator(): \ArrayIterator
+	{
+		return new \ArrayIterator($this->list);
+	}
+
+
+	/**
+	 * Returns items count.
+	 */
+	public function count(): int
+	{
+		return count($this->list);
+	}
+
+
+	/**
+	 * Replaces or appends a item.
+	 * @param  int|null  $index
+	 * @param  mixed  $value
+	 * @throws Nette\OutOfRangeException
+	 */
+	public function offsetSet($index, $value): void
+	{
+		if ($index === null) {
+			$this->list[] = $value;
+
+		} elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) {
+			throw new Nette\OutOfRangeException('Offset invalid or out of range');
+
+		} else {
+			$this->list[$index] = $value;
+		}
+	}
+
+
+	/**
+	 * Returns a item.
+	 * @param  int  $index
+	 * @return mixed
+	 * @throws Nette\OutOfRangeException
+	 */
+	public function offsetGet($index)
+	{
+		if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
+			throw new Nette\OutOfRangeException('Offset invalid or out of range');
+		}
+		return $this->list[$index];
+	}
+
+
+	/**
+	 * Determines whether a item exists.
+	 * @param  int  $index
+	 */
+	public function offsetExists($index): bool
+	{
+		return is_int($index) && $index >= 0 && $index < count($this->list);
+	}
+
+
+	/**
+	 * Removes the element at the specified position in this list.
+	 * @param  int  $index
+	 * @throws Nette\OutOfRangeException
+	 */
+	public function offsetUnset($index): void
+	{
+		if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
+			throw new Nette\OutOfRangeException('Offset invalid or out of range');
+		}
+		array_splice($this->list, $index, 1);
+	}
+
+
+	/**
+	 * Prepends a item.
+	 * @param  mixed  $value
+	 */
+	public function prepend($value): void
+	{
+		$first = array_slice($this->list, 0, 1);
+		$this->offsetSet(0, $value);
+		array_splice($this->list, 1, 0, $first);
+	}
+}

+ 420 - 0
vendor/nette/utils/src/Utils/Arrays.php

@@ -0,0 +1,420 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+use function is_array, is_int, is_object, count;
+
+
+/**
+ * Array tools library.
+ */
+class Arrays
+{
+	use Nette\StaticClass;
+
+	/**
+	 * Returns item from array. If it does not exist, it throws an exception, unless a default value is set.
+	 * @param  string|int|array  $key one or more keys
+	 * @param  mixed  $default
+	 * @return mixed
+	 * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
+	 */
+	public static function get(array $array, $key, $default = null)
+	{
+		foreach (is_array($key) ? $key : [$key] as $k) {
+			if (is_array($array) && array_key_exists($k, $array)) {
+				$array = $array[$k];
+			} else {
+				if (func_num_args() < 3) {
+					throw new Nette\InvalidArgumentException("Missing item '$k'.");
+				}
+				return $default;
+			}
+		}
+		return $array;
+	}
+
+
+	/**
+	 * Returns reference to array item. If the index does not exist, new one is created with value null.
+	 * @param  string|int|array  $key one or more keys
+	 * @return mixed
+	 * @throws Nette\InvalidArgumentException if traversed item is not an array
+	 */
+	public static function &getRef(array &$array, $key)
+	{
+		foreach (is_array($key) ? $key : [$key] as $k) {
+			if (is_array($array) || $array === null) {
+				$array = &$array[$k];
+			} else {
+				throw new Nette\InvalidArgumentException('Traversed item is not an array.');
+			}
+		}
+		return $array;
+	}
+
+
+	/**
+	 * Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
+	 * the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
+	 * the value from the first array in the case of a key collision.
+	 */
+	public static function mergeTree(array $array1, array $array2): array
+	{
+		$res = $array1 + $array2;
+		foreach (array_intersect_key($array1, $array2) as $k => $v) {
+			if (is_array($v) && is_array($array2[$k])) {
+				$res[$k] = self::mergeTree($v, $array2[$k]);
+			}
+		}
+		return $res;
+	}
+
+
+	/**
+	 * Returns zero-indexed position of given array key. Returns null if key is not found.
+	 * @param  string|int  $key
+	 * @return int|null offset if it is found, null otherwise
+	 */
+	public static function getKeyOffset(array $array, $key): ?int
+	{
+		return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true));
+	}
+
+
+	/**
+	 * @deprecated  use  getKeyOffset()
+	 */
+	public static function searchKey(array $array, $key): ?int
+	{
+		return self::getKeyOffset($array, $key);
+	}
+
+
+	/**
+	 * Tests an array for the presence of value.
+	 * @param  mixed  $value
+	 */
+	public static function contains(array $array, $value): bool
+	{
+		return in_array($value, $array, true);
+	}
+
+
+	/**
+	 * Returns the first item from the array or null if array is empty.
+	 * @return mixed
+	 */
+	public static function first(array $array)
+	{
+		return count($array) ? reset($array) : null;
+	}
+
+
+	/**
+	 * Returns the last item from the array or null if array is empty.
+	 * @return mixed
+	 */
+	public static function last(array $array)
+	{
+		return count($array) ? end($array) : null;
+	}
+
+
+	/**
+	 * Inserts the contents of the $inserted array into the $array immediately after the $key.
+	 * If $key is null (or does not exist), it is inserted at the beginning.
+	 * @param  string|int|null  $key
+	 */
+	public static function insertBefore(array &$array, $key, array $inserted): void
+	{
+		$offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key);
+		$array = array_slice($array, 0, $offset, true)
+			+ $inserted
+			+ array_slice($array, $offset, count($array), true);
+	}
+
+
+	/**
+	 * Inserts the contents of the $inserted array into the $array before the $key.
+	 * If $key is null (or does not exist), it is inserted at the end.
+	 * @param  string|int|null  $key
+	 */
+	public static function insertAfter(array &$array, $key, array $inserted): void
+	{
+		if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) {
+			$offset = count($array) - 1;
+		}
+		$array = array_slice($array, 0, $offset + 1, true)
+			+ $inserted
+			+ array_slice($array, $offset + 1, count($array), true);
+	}
+
+
+	/**
+	 * Renames key in array.
+	 * @param  string|int  $oldKey
+	 * @param  string|int  $newKey
+	 */
+	public static function renameKey(array &$array, $oldKey, $newKey): bool
+	{
+		$offset = self::getKeyOffset($array, $oldKey);
+		if ($offset === null) {
+			return false;
+		}
+		$val = &$array[$oldKey];
+		$keys = array_keys($array);
+		$keys[$offset] = $newKey;
+		$array = array_combine($keys, $array);
+		$array[$newKey] = &$val;
+		return true;
+	}
+
+
+	/**
+	 * Returns only those array items, which matches a regular expression $pattern.
+	 * @throws Nette\RegexpException  on compilation or runtime error
+	 */
+	public static function grep(array $array, string $pattern, int $flags = 0): array
+	{
+		return Strings::pcre('preg_grep', [$pattern, $array, $flags]);
+	}
+
+
+	/**
+	 * Transforms multidimensional array to flat array.
+	 */
+	public static function flatten(array $array, bool $preserveKeys = false): array
+	{
+		$res = [];
+		$cb = $preserveKeys
+			? function ($v, $k) use (&$res): void { $res[$k] = $v; }
+		: function ($v) use (&$res): void { $res[] = $v; };
+		array_walk_recursive($array, $cb);
+		return $res;
+	}
+
+
+	/**
+	 * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list.
+	 * @param  mixed  $value
+	 */
+	public static function isList($value): bool
+	{
+		return is_array($value) && (!$value || array_keys($value) === range(0, count($value) - 1));
+	}
+
+
+	/**
+	 * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
+	 * @param  string|string[]  $path
+	 * @return array|\stdClass
+	 */
+	public static function associate(array $array, $path)
+	{
+		$parts = is_array($path)
+			? $path
+			: preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
+
+		if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
+			throw new Nette\InvalidArgumentException("Invalid path '$path'.");
+		}
+
+		$res = $parts[0] === '->' ? new \stdClass : [];
+
+		foreach ($array as $rowOrig) {
+			$row = (array) $rowOrig;
+			$x = &$res;
+
+			for ($i = 0; $i < count($parts); $i++) {
+				$part = $parts[$i];
+				if ($part === '[]') {
+					$x = &$x[];
+
+				} elseif ($part === '=') {
+					if (isset($parts[++$i])) {
+						$x = $row[$parts[$i]];
+						$row = null;
+					}
+
+				} elseif ($part === '->') {
+					if (isset($parts[++$i])) {
+						if ($x === null) {
+							$x = new \stdClass;
+						}
+						$x = &$x->{$row[$parts[$i]]};
+					} else {
+						$row = is_object($rowOrig) ? $rowOrig : (object) $row;
+					}
+
+				} elseif ($part !== '|') {
+					$x = &$x[(string) $row[$part]];
+				}
+			}
+
+			if ($x === null) {
+				$x = $row;
+			}
+		}
+
+		return $res;
+	}
+
+
+	/**
+	 * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
+	 * @param  mixed  $filling
+	 */
+	public static function normalize(array $array, $filling = null): array
+	{
+		$res = [];
+		foreach ($array as $k => $v) {
+			$res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v;
+		}
+		return $res;
+	}
+
+
+	/**
+	 * Returns and removes the value of an item from an array. If it does not exist, it throws an exception,
+	 * or returns $default, if provided.
+	 * @param  string|int  $key
+	 * @param  mixed  $default
+	 * @return mixed
+	 * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided
+	 */
+	public static function pick(array &$array, $key, $default = null)
+	{
+		if (array_key_exists($key, $array)) {
+			$value = $array[$key];
+			unset($array[$key]);
+			return $value;
+
+		} elseif (func_num_args() < 3) {
+			throw new Nette\InvalidArgumentException("Missing item '$key'.");
+
+		} else {
+			return $default;
+		}
+	}
+
+
+	/**
+	 * Tests whether at least one element in the array passes the test implemented by the
+	 * provided callback with signature `function ($value, $key, array $array): bool`.
+	 */
+	public static function some(iterable $array, callable $callback): bool
+	{
+		foreach ($array as $k => $v) {
+			if ($callback($v, $k, $array)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+
+	/**
+	 * Tests whether all elements in the array pass the test implemented by the provided function,
+	 * which has the signature `function ($value, $key, array $array): bool`.
+	 */
+	public static function every(iterable $array, callable $callback): bool
+	{
+		foreach ($array as $k => $v) {
+			if (!$callback($v, $k, $array)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+
+	/**
+	 * Calls $callback on all elements in the array and returns the array of return values.
+	 * The callback has the signature `function ($value, $key, array $array): bool`.
+	 */
+	public static function map(iterable $array, callable $callback): array
+	{
+		$res = [];
+		foreach ($array as $k => $v) {
+			$res[$k] = $callback($v, $k, $array);
+		}
+		return $res;
+	}
+
+
+	/**
+	 * Invokes all callbacks and returns array of results.
+	 * @param  callable[]  $callbacks
+	 */
+	public static function invoke(iterable $callbacks, ...$args): array
+	{
+		$res = [];
+		foreach ($callbacks as $k => $cb) {
+			$res[$k] = $cb(...$args);
+		}
+		return $res;
+	}
+
+
+	/**
+	 * Invokes method on every object in an array and returns array of results.
+	 * @param  object[]  $objects
+	 */
+	public static function invokeMethod(iterable $objects, string $method, ...$args): array
+	{
+		$res = [];
+		foreach ($objects as $k => $obj) {
+			$res[$k] = $obj->$method(...$args);
+		}
+		return $res;
+	}
+
+
+	/**
+	 * Copies the elements of the $array array to the $object object and then returns it.
+	 * @param  object  $object
+	 * @return object
+	 */
+	public static function toObject(iterable $array, $object)
+	{
+		foreach ($array as $k => $v) {
+			$object->$k = $v;
+		}
+		return $object;
+	}
+
+
+	/**
+	 * Converts value to array key.
+	 * @param  mixed  $value
+	 * @return int|string
+	 */
+	public static function toKey($value)
+	{
+		return key([$value => null]);
+	}
+
+
+	/**
+	 * Returns copy of the $array where every item is converted to string
+	 * and prefixed by $prefix and suffixed by $suffix.
+	 * @return string[]
+	 */
+	public static function wrap(array $array, string $prefix = '', string $suffix = ''): array
+	{
+		$res = [];
+		foreach ($array as $k => $v) {
+			$res[$k] = $prefix . $v . $suffix;
+		}
+		return $res;
+	}
+}

+ 181 - 0
vendor/nette/utils/src/Utils/Callback.php

@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+use function is_array, is_object, is_string;
+
+
+/**
+ * PHP callable tools.
+ */
+final class Callback
+{
+	use Nette\StaticClass;
+
+	/**
+	 * @param  string|object|callable  $callable  class, object, callable
+	 * @deprecated use Closure::fromCallable()
+	 */
+	public static function closure($callable, string $method = null): \Closure
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use Closure::fromCallable().', E_USER_DEPRECATED);
+		try {
+			return \Closure::fromCallable($method === null ? $callable : [$callable, $method]);
+		} catch (\TypeError $e) {
+			throw new Nette\InvalidArgumentException($e->getMessage());
+		}
+	}
+
+
+	/**
+	 * Invokes callback.
+	 * @return mixed
+	 * @deprecated
+	 */
+	public static function invoke($callable, ...$args)
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
+		self::check($callable);
+		return $callable(...$args);
+	}
+
+
+	/**
+	 * Invokes callback with an array of parameters.
+	 * @return mixed
+	 * @deprecated
+	 */
+	public static function invokeArgs($callable, array $args = [])
+	{
+		trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED);
+		self::check($callable);
+		return $callable(...$args);
+	}
+
+
+	/**
+	 * Invokes internal PHP function with own error handler.
+	 * @return mixed
+	 */
+	public static function invokeSafe(string $function, array $args, callable $onError)
+	{
+		$prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
+			if ($file === __FILE__) {
+				$msg = ini_get('html_errors')
+					? Html::htmlToText($message)
+					: $message;
+				$msg = preg_replace("#^$function\\(.*?\\): #", '', $msg);
+				if ($onError($msg, $severity) !== false) {
+					return null;
+				}
+			}
+			return $prev ? $prev(...func_get_args()) : false;
+		});
+
+		try {
+			return $function(...$args);
+		} finally {
+			restore_error_handler();
+		}
+	}
+
+
+	/**
+	 * Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies
+	 * that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists.
+	 * @param  mixed  $callable
+	 * @return callable
+	 * @throws Nette\InvalidArgumentException
+	 */
+	public static function check($callable, bool $syntax = false)
+	{
+		if (!is_callable($callable, $syntax)) {
+			throw new Nette\InvalidArgumentException(
+				$syntax
+				? 'Given value is not a callable type.'
+				: sprintf("Callback '%s' is not callable.", self::toString($callable))
+			);
+		}
+		return $callable;
+	}
+
+
+	/**
+	 * Converts PHP callback to textual form. Class or method may not exists.
+	 * @param  mixed  $callable
+	 */
+	public static function toString($callable): string
+	{
+		if ($callable instanceof \Closure) {
+			$inner = self::unwrap($callable);
+			return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}');
+		} elseif (is_string($callable) && $callable[0] === "\0") {
+			return '{lambda}';
+		} else {
+			is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual);
+			return $textual;
+		}
+	}
+
+
+	/**
+	 * Returns reflection for method or function used in PHP callback.
+	 * @param  callable  $callable  type check is escalated to ReflectionException
+	 * @return \ReflectionMethod|\ReflectionFunction
+	 * @throws \ReflectionException  if callback is not valid
+	 */
+	public static function toReflection($callable): \ReflectionFunctionAbstract
+	{
+		if ($callable instanceof \Closure) {
+			$callable = self::unwrap($callable);
+		}
+
+		if (is_string($callable) && strpos($callable, '::')) {
+			return new \ReflectionMethod($callable);
+		} elseif (is_array($callable)) {
+			return new \ReflectionMethod($callable[0], $callable[1]);
+		} elseif (is_object($callable) && !$callable instanceof \Closure) {
+			return new \ReflectionMethod($callable, '__invoke');
+		} else {
+			return new \ReflectionFunction($callable);
+		}
+	}
+
+
+	/**
+	 * Checks whether PHP callback is function or static method.
+	 */
+	public static function isStatic(callable $callable): bool
+	{
+		return is_array($callable) ? is_string($callable[0]) : is_string($callable);
+	}
+
+
+	/**
+	 * Unwraps closure created by Closure::fromCallable().
+	 */
+	public static function unwrap(\Closure $closure): callable
+	{
+		$r = new \ReflectionFunction($closure);
+		if (substr($r->name, -1) === '}') {
+			return $closure;
+
+		} elseif ($obj = $r->getClosureThis()) {
+			return [$obj, $r->name];
+
+		} elseif ($class = $r->getClosureScopeClass()) {
+			return [$class->name, $r->name];
+
+		} else {
+			return $r->name;
+		}
+	}
+}

+ 144 - 0
vendor/nette/utils/src/Utils/DateTime.php

@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * DateTime.
+ */
+class DateTime extends \DateTime implements \JsonSerializable
+{
+	use Nette\SmartObject;
+
+	/** minute in seconds */
+	public const MINUTE = 60;
+
+	/** hour in seconds */
+	public const HOUR = 60 * self::MINUTE;
+
+	/** day in seconds */
+	public const DAY = 24 * self::HOUR;
+
+	/** week in seconds */
+	public const WEEK = 7 * self::DAY;
+
+	/** average month in seconds */
+	public const MONTH = 2629800;
+
+	/** average year in seconds */
+	public const YEAR = 31557600;
+
+
+	/**
+	 * Creates a DateTime object from a string, UNIX timestamp, or other DateTimeInterface object.
+	 * @param  string|int|\DateTimeInterface  $time
+	 * @return static
+	 * @throws \Exception if the date and time are not valid.
+	 */
+	public static function from($time)
+	{
+		if ($time instanceof \DateTimeInterface) {
+			return new static($time->format('Y-m-d H:i:s.u'), $time->getTimezone());
+
+		} elseif (is_numeric($time)) {
+			if ($time <= self::YEAR) {
+				$time += time();
+			}
+			return (new static('@' . $time))->setTimezone(new \DateTimeZone(date_default_timezone_get()));
+
+		} else { // textual or null
+			return new static((string) $time);
+		}
+	}
+
+
+	/**
+	 * Creates DateTime object.
+	 * @return static
+	 * @throws Nette\InvalidArgumentException if the date and time are not valid.
+	 */
+	public static function fromParts(
+		int $year,
+		int $month,
+		int $day,
+		int $hour = 0,
+		int $minute = 0,
+		float $second = 0.0
+	) {
+		$s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second);
+		if (
+			!checkdate($month, $day, $year)
+			|| $hour < 0
+			|| $hour > 23
+			|| $minute < 0
+			|| $minute > 59
+			|| $second < 0
+			|| $second >= 60
+		) {
+			throw new Nette\InvalidArgumentException("Invalid date '$s'");
+		}
+		return new static($s);
+	}
+
+
+	/**
+	 * Returns new DateTime object formatted according to the specified format.
+	 * @param  string  $format  The format the $time parameter should be in
+	 * @param  string  $time
+	 * @param  string|\DateTimeZone  $timezone (default timezone is used if null is passed)
+	 * @return static|false
+	 */
+	public static function createFromFormat($format, $time, $timezone = null)
+	{
+		if ($timezone === null) {
+			$timezone = new \DateTimeZone(date_default_timezone_get());
+
+		} elseif (is_string($timezone)) {
+			$timezone = new \DateTimeZone($timezone);
+
+		} elseif (!$timezone instanceof \DateTimeZone) {
+			throw new Nette\InvalidArgumentException('Invalid timezone given');
+		}
+
+		$date = parent::createFromFormat($format, $time, $timezone);
+		return $date ? static::from($date) : false;
+	}
+
+
+	/**
+	 * Returns JSON representation in ISO 8601 (used by JavaScript).
+	 */
+	public function jsonSerialize(): string
+	{
+		return $this->format('c');
+	}
+
+
+	/**
+	 * Returns the date and time in the format 'Y-m-d H:i:s'.
+	 */
+	public function __toString(): string
+	{
+		return $this->format('Y-m-d H:i:s');
+	}
+
+
+	/**
+	 * Creates a copy with a modified time.
+	 * @return static
+	 */
+	public function modifyClone(string $modify = '')
+	{
+		$dolly = clone $this;
+		return $modify ? $dolly->modify($modify) : $dolly;
+	}
+}

+ 210 - 0
vendor/nette/utils/src/Utils/FileSystem.php

@@ -0,0 +1,210 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * File system tool.
+ */
+final class FileSystem
+{
+	use Nette\StaticClass;
+
+	/**
+	 * Creates a directory if it doesn't exist.
+	 * @throws Nette\IOException  on error occurred
+	 */
+	public static function createDir(string $dir, int $mode = 0777): void
+	{
+		if (!is_dir($dir) && !@mkdir($dir, $mode, true) && !is_dir($dir)) { // @ - dir may already exist
+			throw new Nette\IOException("Unable to create directory '$dir' with mode " . decoct($mode) . '. ' . Helpers::getLastError());
+		}
+	}
+
+
+	/**
+	 * Copies a file or a directory. Overwrites existing files and directories by default.
+	 * @throws Nette\IOException  on error occurred
+	 * @throws Nette\InvalidStateException  if $overwrite is set to false and destination already exists
+	 */
+	public static function copy(string $origin, string $target, bool $overwrite = true): void
+	{
+		if (stream_is_local($origin) && !file_exists($origin)) {
+			throw new Nette\IOException("File or directory '$origin' not found.");
+
+		} elseif (!$overwrite && file_exists($target)) {
+			throw new Nette\InvalidStateException("File or directory '$target' already exists.");
+
+		} elseif (is_dir($origin)) {
+			static::createDir($target);
+			foreach (new \FilesystemIterator($target) as $item) {
+				static::delete($item->getPathname());
+			}
+			foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
+				if ($item->isDir()) {
+					static::createDir($target . '/' . $iterator->getSubPathName());
+				} else {
+					static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
+				}
+			}
+
+		} else {
+			static::createDir(dirname($target));
+			if (
+				($s = @fopen($origin, 'rb'))
+				&& ($d = @fopen($target, 'wb'))
+				&& @stream_copy_to_stream($s, $d) === false
+			) { // @ is escalated to exception
+				throw new Nette\IOException("Unable to copy file '$origin' to '$target'. " . Helpers::getLastError());
+			}
+		}
+	}
+
+
+	/**
+	 * Deletes a file or directory if exists.
+	 * @throws Nette\IOException  on error occurred
+	 */
+	public static function delete(string $path): void
+	{
+		if (is_file($path) || is_link($path)) {
+			$func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink';
+			if (!@$func($path)) { // @ is escalated to exception
+				throw new Nette\IOException("Unable to delete '$path'. " . Helpers::getLastError());
+			}
+
+		} elseif (is_dir($path)) {
+			foreach (new \FilesystemIterator($path) as $item) {
+				static::delete($item->getPathname());
+			}
+			if (!@rmdir($path)) { // @ is escalated to exception
+				throw new Nette\IOException("Unable to delete directory '$path'. " . Helpers::getLastError());
+			}
+		}
+	}
+
+
+	/**
+	 * Renames or moves a file or a directory. Overwrites existing files and directories by default.
+	 * @throws Nette\IOException  on error occurred
+	 * @throws Nette\InvalidStateException  if $overwrite is set to false and destination already exists
+	 */
+	public static function rename(string $origin, string $target, bool $overwrite = true): void
+	{
+		if (!$overwrite && file_exists($target)) {
+			throw new Nette\InvalidStateException("File or directory '$target' already exists.");
+
+		} elseif (!file_exists($origin)) {
+			throw new Nette\IOException("File or directory '$origin' not found.");
+
+		} else {
+			static::createDir(dirname($target));
+			if (realpath($origin) !== realpath($target)) {
+				static::delete($target);
+			}
+			if (!@rename($origin, $target)) { // @ is escalated to exception
+				throw new Nette\IOException("Unable to rename file or directory '$origin' to '$target'. " . Helpers::getLastError());
+			}
+		}
+	}
+
+
+	/**
+	 * Reads the content of a file.
+	 * @throws Nette\IOException  on error occurred
+	 */
+	public static function read(string $file): string
+	{
+		$content = @file_get_contents($file); // @ is escalated to exception
+		if ($content === false) {
+			throw new Nette\IOException("Unable to read file '$file'. " . Helpers::getLastError());
+		}
+		return $content;
+	}
+
+
+	/**
+	 * Writes the string to a file.
+	 * @throws Nette\IOException  on error occurred
+	 */
+	public static function write(string $file, string $content, ?int $mode = 0666): void
+	{
+		static::createDir(dirname($file));
+		if (@file_put_contents($file, $content) === false) { // @ is escalated to exception
+			throw new Nette\IOException("Unable to write file '$file'. " . Helpers::getLastError());
+		}
+		if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception
+			throw new Nette\IOException("Unable to chmod file '$file' to mode " . decoct($mode) . '. ' . Helpers::getLastError());
+		}
+	}
+
+
+	/**
+	 * Fixes permissions to a specific file or directory. Directories can be fixed recursively.
+	 * @throws Nette\IOException  on error occurred
+	 */
+	public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void
+	{
+		if (is_file($path)) {
+			if (!@chmod($path, $fileMode)) { // @ is escalated to exception
+				throw new Nette\IOException("Unable to chmod file '$path' to mode " . decoct($fileMode) . '. ' . Helpers::getLastError());
+			}
+		} elseif (is_dir($path)) {
+			foreach (new \FilesystemIterator($path) as $item) {
+				static::makeWritable($item->getPathname(), $dirMode, $fileMode);
+			}
+			if (!@chmod($path, $dirMode)) { // @ is escalated to exception
+				throw new Nette\IOException("Unable to chmod directory '$path' to mode " . decoct($dirMode) . '. ' . Helpers::getLastError());
+			}
+		} else {
+			throw new Nette\IOException("File or directory '$path' not found.");
+		}
+	}
+
+
+	/**
+	 * Determines if the path is absolute.
+	 */
+	public static function isAbsolute(string $path): bool
+	{
+		return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
+	}
+
+
+	/**
+	 * Normalizes `..` and `.` and directory separators in path.
+	 */
+	public static function normalizePath(string $path): string
+	{
+		$parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path);
+		$res = [];
+		foreach ($parts as $part) {
+			if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') {
+				array_pop($res);
+			} elseif ($part !== '.') {
+				$res[] = $part;
+			}
+		}
+		return $res === ['']
+			? DIRECTORY_SEPARATOR
+			: implode(DIRECTORY_SEPARATOR, $res);
+	}
+
+
+	/**
+	 * Joins all segments of the path and normalizes the result.
+	 */
+	public static function joinPaths(string ...$paths): string
+	{
+		return self::normalizePath(implode('/', $paths));
+	}
+}

+ 107 - 0
vendor/nette/utils/src/Utils/Floats.php

@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Floating-point numbers comparison.
+ */
+class Floats
+{
+	use Nette\StaticClass;
+
+	private const EPSILON = 1e-10;
+
+
+	public static function isZero(float $value): bool
+	{
+		return abs($value) < self::EPSILON;
+	}
+
+
+	public static function isInteger(float $value): bool
+	{
+		return abs(round($value) - $value) < self::EPSILON;
+	}
+
+
+	/**
+	 * Compare two floats. If $a < $b it returns -1, if they are equal it returns 0 and if $a > $b it returns 1
+	 * @throws \LogicException if one of parameters is NAN
+	 */
+	public static function compare(float $a, float $b): int
+	{
+		if (is_nan($a) || is_nan($b)) {
+			throw new \LogicException('Trying to compare NAN');
+
+		} elseif (!is_finite($a) && !is_finite($b) && $a === $b) {
+			return 0;
+		}
+
+		$diff = abs($a - $b);
+		if (($diff < self::EPSILON || ($diff / max(abs($a), abs($b)) < self::EPSILON))) {
+			return 0;
+		}
+
+		return $a < $b ? -1 : 1;
+	}
+
+
+	/**
+	 * Returns true if $a = $b
+	 * @throws \LogicException if one of parameters is NAN
+	 */
+	public static function areEqual(float $a, float $b): bool
+	{
+		return self::compare($a, $b) === 0;
+	}
+
+
+	/**
+	 * Returns true if $a < $b
+	 * @throws \LogicException if one of parameters is NAN
+	 */
+	public static function isLessThan(float $a, float $b): bool
+	{
+		return self::compare($a, $b) < 0;
+	}
+
+
+	/**
+	 * Returns true if $a <= $b
+	 * @throws \LogicException if one of parameters is NAN
+	 */
+	public static function isLessThanOrEqualTo(float $a, float $b): bool
+	{
+		return self::compare($a, $b) <= 0;
+	}
+
+
+	/**
+	 * Returns true if $a > $b
+	 * @throws \LogicException if one of parameters is NAN
+	 */
+	public static function isGreaterThan(float $a, float $b): bool
+	{
+		return self::compare($a, $b) > 0;
+	}
+
+
+	/**
+	 * Returns true if $a >= $b
+	 * @throws \LogicException if one of parameters is NAN
+	 */
+	public static function isGreaterThanOrEqualTo(float $a, float $b): bool
+	{
+		return self::compare($a, $b) >= 0;
+	}
+}

+ 71 - 0
vendor/nette/utils/src/Utils/Helpers.php

@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+
+class Helpers
+{
+	/**
+	 * Executes a callback and returns the captured output as a string.
+	 */
+	public static function capture(callable $func): string
+	{
+		ob_start(function () {});
+		try {
+			$func();
+			return ob_get_clean();
+		} catch (\Throwable $e) {
+			ob_end_clean();
+			throw $e;
+		}
+	}
+
+
+	/**
+	 * Returns the last occurred PHP error or an empty string if no error occurred. Unlike error_get_last(),
+	 * it is nit affected by the PHP directive html_errors and always returns text, not HTML.
+	 */
+	public static function getLastError(): string
+	{
+		$message = error_get_last()['message'] ?? '';
+		$message = ini_get('html_errors') ? Html::htmlToText($message) : $message;
+		$message = preg_replace('#^\w+\(.*?\): #', '', $message);
+		return $message;
+	}
+
+
+	/**
+	 * Converts false to null, does not change other values.
+	 * @param  mixed  $value
+	 * @return mixed
+	 */
+	public static function falseToNull($value)
+	{
+		return $value === false ? null : $value;
+	}
+
+
+	/**
+	 * Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding).
+	 * @param  string[]  $possibilities
+	 */
+	public static function getSuggestion(array $possibilities, string $value): ?string
+	{
+		$best = null;
+		$min = (strlen($value) / 4 + 1) * 10 + .1;
+		foreach (array_unique($possibilities) as $item) {
+			if ($item !== $value && ($len = levenshtein($item, $value, 10, 11, 10)) < $min) {
+				$min = $len;
+				$best = $item;
+			}
+		}
+		return $best;
+	}
+}

+ 875 - 0
vendor/nette/utils/src/Utils/Html.php

@@ -0,0 +1,875 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+use Nette\HtmlStringable;
+use function is_array, is_float, is_object, is_string;
+
+
+/**
+ * HTML helper.
+ *
+ * @property string|null $accept
+ * @property string|null $accesskey
+ * @property string|null $action
+ * @property string|null $align
+ * @property string|null $allow
+ * @property string|null $alt
+ * @property bool|null   $async
+ * @property string|null $autocapitalize
+ * @property string|null $autocomplete
+ * @property bool|null   $autofocus
+ * @property bool|null   $autoplay
+ * @property string|null $charset
+ * @property bool|null   $checked
+ * @property string|null $cite
+ * @property string|null $class
+ * @property int|null    $cols
+ * @property int|null    $colspan
+ * @property string|null $content
+ * @property bool|null   $contenteditable
+ * @property bool|null   $controls
+ * @property string|null $coords
+ * @property string|null $crossorigin
+ * @property string|null $data
+ * @property string|null $datetime
+ * @property string|null $decoding
+ * @property bool|null   $default
+ * @property bool|null   $defer
+ * @property string|null $dir
+ * @property string|null $dirname
+ * @property bool|null   $disabled
+ * @property bool|null   $download
+ * @property string|null $draggable
+ * @property string|null $dropzone
+ * @property string|null $enctype
+ * @property string|null $for
+ * @property string|null $form
+ * @property string|null $formaction
+ * @property string|null $formenctype
+ * @property string|null $formmethod
+ * @property bool|null   $formnovalidate
+ * @property string|null $formtarget
+ * @property string|null $headers
+ * @property int|null    $height
+ * @property bool|null   $hidden
+ * @property float|null  $high
+ * @property string|null $href
+ * @property string|null $hreflang
+ * @property string|null $id
+ * @property string|null $integrity
+ * @property string|null $inputmode
+ * @property bool|null   $ismap
+ * @property string|null $itemprop
+ * @property string|null $kind
+ * @property string|null $label
+ * @property string|null $lang
+ * @property string|null $list
+ * @property bool|null   $loop
+ * @property float|null  $low
+ * @property float|null  $max
+ * @property int|null    $maxlength
+ * @property int|null    $minlength
+ * @property string|null $media
+ * @property string|null $method
+ * @property float|null  $min
+ * @property bool|null   $multiple
+ * @property bool|null   $muted
+ * @property string|null $name
+ * @property bool|null   $novalidate
+ * @property bool|null   $open
+ * @property float|null  $optimum
+ * @property string|null $pattern
+ * @property string|null $ping
+ * @property string|null $placeholder
+ * @property string|null $poster
+ * @property string|null $preload
+ * @property string|null $radiogroup
+ * @property bool|null   $readonly
+ * @property string|null $rel
+ * @property bool|null   $required
+ * @property bool|null   $reversed
+ * @property int|null    $rows
+ * @property int|null    $rowspan
+ * @property string|null $sandbox
+ * @property string|null $scope
+ * @property bool|null   $selected
+ * @property string|null $shape
+ * @property int|null    $size
+ * @property string|null $sizes
+ * @property string|null $slot
+ * @property int|null    $span
+ * @property string|null $spellcheck
+ * @property string|null $src
+ * @property string|null $srcdoc
+ * @property string|null $srclang
+ * @property string|null $srcset
+ * @property int|null    $start
+ * @property float|null  $step
+ * @property string|null $style
+ * @property int|null    $tabindex
+ * @property string|null $target
+ * @property string|null $title
+ * @property string|null $translate
+ * @property string|null $type
+ * @property string|null $usemap
+ * @property string|null $value
+ * @property int|null    $width
+ * @property string|null $wrap
+ *
+ * @method self accept(?string $val)
+ * @method self accesskey(?string $val, bool $state = null)
+ * @method self action(?string $val)
+ * @method self align(?string $val)
+ * @method self allow(?string $val, bool $state = null)
+ * @method self alt(?string $val)
+ * @method self async(?bool $val)
+ * @method self autocapitalize(?string $val)
+ * @method self autocomplete(?string $val)
+ * @method self autofocus(?bool $val)
+ * @method self autoplay(?bool $val)
+ * @method self charset(?string $val)
+ * @method self checked(?bool $val)
+ * @method self cite(?string $val)
+ * @method self class(?string $val, bool $state = null)
+ * @method self cols(?int $val)
+ * @method self colspan(?int $val)
+ * @method self content(?string $val)
+ * @method self contenteditable(?bool $val)
+ * @method self controls(?bool $val)
+ * @method self coords(?string $val)
+ * @method self crossorigin(?string $val)
+ * @method self datetime(?string $val)
+ * @method self decoding(?string $val)
+ * @method self default(?bool $val)
+ * @method self defer(?bool $val)
+ * @method self dir(?string $val)
+ * @method self dirname(?string $val)
+ * @method self disabled(?bool $val)
+ * @method self download(?bool $val)
+ * @method self draggable(?string $val)
+ * @method self dropzone(?string $val)
+ * @method self enctype(?string $val)
+ * @method self for(?string $val)
+ * @method self form(?string $val)
+ * @method self formaction(?string $val)
+ * @method self formenctype(?string $val)
+ * @method self formmethod(?string $val)
+ * @method self formnovalidate(?bool $val)
+ * @method self formtarget(?string $val)
+ * @method self headers(?string $val, bool $state = null)
+ * @method self height(?int $val)
+ * @method self hidden(?bool $val)
+ * @method self high(?float $val)
+ * @method self hreflang(?string $val)
+ * @method self id(?string $val)
+ * @method self integrity(?string $val)
+ * @method self inputmode(?string $val)
+ * @method self ismap(?bool $val)
+ * @method self itemprop(?string $val)
+ * @method self kind(?string $val)
+ * @method self label(?string $val)
+ * @method self lang(?string $val)
+ * @method self list(?string $val)
+ * @method self loop(?bool $val)
+ * @method self low(?float $val)
+ * @method self max(?float $val)
+ * @method self maxlength(?int $val)
+ * @method self minlength(?int $val)
+ * @method self media(?string $val)
+ * @method self method(?string $val)
+ * @method self min(?float $val)
+ * @method self multiple(?bool $val)
+ * @method self muted(?bool $val)
+ * @method self name(?string $val)
+ * @method self novalidate(?bool $val)
+ * @method self open(?bool $val)
+ * @method self optimum(?float $val)
+ * @method self pattern(?string $val)
+ * @method self ping(?string $val, bool $state = null)
+ * @method self placeholder(?string $val)
+ * @method self poster(?string $val)
+ * @method self preload(?string $val)
+ * @method self radiogroup(?string $val)
+ * @method self readonly(?bool $val)
+ * @method self rel(?string $val)
+ * @method self required(?bool $val)
+ * @method self reversed(?bool $val)
+ * @method self rows(?int $val)
+ * @method self rowspan(?int $val)
+ * @method self sandbox(?string $val, bool $state = null)
+ * @method self scope(?string $val)
+ * @method self selected(?bool $val)
+ * @method self shape(?string $val)
+ * @method self size(?int $val)
+ * @method self sizes(?string $val)
+ * @method self slot(?string $val)
+ * @method self span(?int $val)
+ * @method self spellcheck(?string $val)
+ * @method self src(?string $val)
+ * @method self srcdoc(?string $val)
+ * @method self srclang(?string $val)
+ * @method self srcset(?string $val)
+ * @method self start(?int $val)
+ * @method self step(?float $val)
+ * @method self style(?string $property, string $val = null)
+ * @method self tabindex(?int $val)
+ * @method self target(?string $val)
+ * @method self title(?string $val)
+ * @method self translate(?string $val)
+ * @method self type(?string $val)
+ * @method self usemap(?string $val)
+ * @method self value(?string $val)
+ * @method self width(?int $val)
+ * @method self wrap(?string $val)
+ */
+class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable
+{
+	use Nette\SmartObject;
+
+	/** @var array<string, mixed>  element's attributes */
+	public $attrs = [];
+
+	/** @var bool  use XHTML syntax? */
+	public static $xhtml = false;
+
+	/** @var array<string, int>  void elements */
+	public static $emptyElements = [
+		'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
+		'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
+		'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
+	];
+
+	/** @var array<int, Html|string> nodes */
+	protected $children = [];
+
+	/** @var string  element's name */
+	private $name;
+
+	/** @var bool  is element empty? */
+	private $isEmpty;
+
+
+	/**
+	 * Constructs new HTML element.
+	 * @param  array|string $attrs element's attributes or plain text content
+	 * @return static
+	 */
+	public static function el(string $name = null, $attrs = null)
+	{
+		$el = new static;
+		$parts = explode(' ', (string) $name, 2);
+		$el->setName($parts[0]);
+
+		if (is_array($attrs)) {
+			$el->attrs = $attrs;
+
+		} elseif ($attrs !== null) {
+			$el->setText($attrs);
+		}
+
+		if (isset($parts[1])) {
+			foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) {
+				$el->attrs[$m[1]] = $m[3] ?? true;
+			}
+		}
+
+		return $el;
+	}
+
+
+	/**
+	 * Returns an object representing HTML text.
+	 */
+	public static function fromHtml(string $html): self
+	{
+		return (new static)->setHtml($html);
+	}
+
+
+	/**
+	 * Returns an object representing plain text.
+	 */
+	public static function fromText(string $text): self
+	{
+		return (new static)->setText($text);
+	}
+
+
+	/**
+	 * Converts to HTML.
+	 */
+	final public function toHtml(): string
+	{
+		return $this->render();
+	}
+
+
+	/**
+	 * Converts to plain text.
+	 */
+	final public function toText(): string
+	{
+		return $this->getText();
+	}
+
+
+	/**
+	 * Converts given HTML code to plain text.
+	 */
+	public static function htmlToText(string $html): string
+	{
+		return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8');
+	}
+
+
+	/**
+	 * Changes element's name.
+	 * @return static
+	 */
+	final public function setName(string $name, bool $isEmpty = null)
+	{
+		$this->name = $name;
+		$this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]);
+		return $this;
+	}
+
+
+	/**
+	 * Returns element's name.
+	 */
+	final public function getName(): string
+	{
+		return $this->name;
+	}
+
+
+	/**
+	 * Is element empty?
+	 */
+	final public function isEmpty(): bool
+	{
+		return $this->isEmpty;
+	}
+
+
+	/**
+	 * Sets multiple attributes.
+	 * @return static
+	 */
+	public function addAttributes(array $attrs)
+	{
+		$this->attrs = array_merge($this->attrs, $attrs);
+		return $this;
+	}
+
+
+	/**
+	 * Appends value to element's attribute.
+	 * @param  mixed  $value
+	 * @param  mixed  $option
+	 * @return static
+	 */
+	public function appendAttribute(string $name, $value, $option = true)
+	{
+		if (is_array($value)) {
+			$prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
+			$this->attrs[$name] = $value + $prev;
+
+		} elseif ((string) $value === '') {
+			$tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists
+
+		} elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array
+			$this->attrs[$name][$value] = $option;
+
+		} else {
+			$this->attrs[$name] = [$this->attrs[$name] => true, $value => $option];
+		}
+		return $this;
+	}
+
+
+	/**
+	 * Sets element's attribute.
+	 * @param  mixed  $value
+	 * @return static
+	 */
+	public function setAttribute(string $name, $value)
+	{
+		$this->attrs[$name] = $value;
+		return $this;
+	}
+
+
+	/**
+	 * Returns element's attribute.
+	 * @return mixed
+	 */
+	public function getAttribute(string $name)
+	{
+		return $this->attrs[$name] ?? null;
+	}
+
+
+	/**
+	 * Unsets element's attribute.
+	 * @return static
+	 */
+	public function removeAttribute(string $name)
+	{
+		unset($this->attrs[$name]);
+		return $this;
+	}
+
+
+	/**
+	 * Unsets element's attributes.
+	 * @return static
+	 */
+	public function removeAttributes(array $attributes)
+	{
+		foreach ($attributes as $name) {
+			unset($this->attrs[$name]);
+		}
+		return $this;
+	}
+
+
+	/**
+	 * Overloaded setter for element's attribute.
+	 * @param  mixed  $value
+	 */
+	final public function __set(string $name, $value): void
+	{
+		$this->attrs[$name] = $value;
+	}
+
+
+	/**
+	 * Overloaded getter for element's attribute.
+	 * @return mixed
+	 */
+	final public function &__get(string $name)
+	{
+		return $this->attrs[$name];
+	}
+
+
+	/**
+	 * Overloaded tester for element's attribute.
+	 */
+	final public function __isset(string $name): bool
+	{
+		return isset($this->attrs[$name]);
+	}
+
+
+	/**
+	 * Overloaded unsetter for element's attribute.
+	 */
+	final public function __unset(string $name): void
+	{
+		unset($this->attrs[$name]);
+	}
+
+
+	/**
+	 * Overloaded setter for element's attribute.
+	 * @return mixed
+	 */
+	final public function __call(string $m, array $args)
+	{
+		$p = substr($m, 0, 3);
+		if ($p === 'get' || $p === 'set' || $p === 'add') {
+			$m = substr($m, 3);
+			$m[0] = $m[0] | "\x20";
+			if ($p === 'get') {
+				return $this->attrs[$m] ?? null;
+
+			} elseif ($p === 'add') {
+				$args[] = true;
+			}
+		}
+
+		if (count($args) === 0) { // invalid
+
+		} elseif (count($args) === 1) { // set
+			$this->attrs[$m] = $args[0];
+
+		} else { // add
+			$this->appendAttribute($m, $args[0], $args[1]);
+		}
+
+		return $this;
+	}
+
+
+	/**
+	 * Special setter for element's attribute.
+	 * @return static
+	 */
+	final public function href(string $path, array $query = null)
+	{
+		if ($query) {
+			$query = http_build_query($query, '', '&');
+			if ($query !== '') {
+				$path .= '?' . $query;
+			}
+		}
+		$this->attrs['href'] = $path;
+		return $this;
+	}
+
+
+	/**
+	 * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
+	 * @param  mixed  $value
+	 * @return static
+	 */
+	public function data(string $name, $value = null)
+	{
+		if (func_num_args() === 1) {
+			$this->attrs['data'] = $name;
+		} else {
+			$this->attrs["data-$name"] = is_bool($value)
+				? json_encode($value)
+				: $value;
+		}
+		return $this;
+	}
+
+
+	/**
+	 * Sets element's HTML content.
+	 * @param  HtmlStringable|string  $html
+	 * @return static
+	 */
+	final public function setHtml($html)
+	{
+		$this->children = [(string) $html];
+		return $this;
+	}
+
+
+	/**
+	 * Returns element's HTML content.
+	 */
+	final public function getHtml(): string
+	{
+		return implode('', $this->children);
+	}
+
+
+	/**
+	 * Sets element's textual content.
+	 * @param  HtmlStringable|string|int|float  $text
+	 * @return static
+	 */
+	final public function setText($text)
+	{
+		if (!$text instanceof HtmlStringable) {
+			$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
+		}
+		$this->children = [(string) $text];
+		return $this;
+	}
+
+
+	/**
+	 * Returns element's textual content.
+	 */
+	final public function getText(): string
+	{
+		return self::htmlToText($this->getHtml());
+	}
+
+
+	/**
+	 * Adds new element's child.
+	 * @param  HtmlStringable|string  $child  Html node or raw HTML string
+	 * @return static
+	 */
+	final public function addHtml($child)
+	{
+		return $this->insert(null, $child);
+	}
+
+
+	/**
+	 * Appends plain-text string to element content.
+	 * @param  HtmlStringable|string|int|float  $text
+	 * @return static
+	 */
+	public function addText($text)
+	{
+		if (!$text instanceof HtmlStringable) {
+			$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
+		}
+		return $this->insert(null, $text);
+	}
+
+
+	/**
+	 * Creates and adds a new Html child.
+	 * @param  array|string $attrs  element's attributes or raw HTML string
+	 * @return static  created element
+	 */
+	final public function create(string $name, $attrs = null)
+	{
+		$this->insert(null, $child = static::el($name, $attrs));
+		return $child;
+	}
+
+
+	/**
+	 * Inserts child node.
+	 * @param  HtmlStringable|string $child Html node or raw HTML string
+	 * @return static
+	 */
+	public function insert(?int $index, $child, bool $replace = false)
+	{
+		$child = $child instanceof self ? $child : (string) $child;
+		if ($index === null) { // append
+			$this->children[] = $child;
+
+		} else { // insert or replace
+			array_splice($this->children, $index, $replace ? 1 : 0, [$child]);
+		}
+
+		return $this;
+	}
+
+
+	/**
+	 * Inserts (replaces) child node (\ArrayAccess implementation).
+	 * @param  int|null  $index  position or null for appending
+	 * @param  Html|string  $child  Html node or raw HTML string
+	 */
+	final public function offsetSet($index, $child): void
+	{
+		$this->insert($index, $child, true);
+	}
+
+
+	/**
+	 * Returns child node (\ArrayAccess implementation).
+	 * @param  int  $index
+	 * @return static|string
+	 */
+	final public function offsetGet($index)
+	{
+		return $this->children[$index];
+	}
+
+
+	/**
+	 * Exists child node? (\ArrayAccess implementation).
+	 * @param  int  $index
+	 */
+	final public function offsetExists($index): bool
+	{
+		return isset($this->children[$index]);
+	}
+
+
+	/**
+	 * Removes child node (\ArrayAccess implementation).
+	 * @param  int  $index
+	 */
+	public function offsetUnset($index): void
+	{
+		if (isset($this->children[$index])) {
+			array_splice($this->children, $index, 1);
+		}
+	}
+
+
+	/**
+	 * Returns children count.
+	 */
+	final public function count(): int
+	{
+		return count($this->children);
+	}
+
+
+	/**
+	 * Removes all children.
+	 */
+	public function removeChildren(): void
+	{
+		$this->children = [];
+	}
+
+
+	/**
+	 * Iterates over elements.
+	 */
+	final public function getIterator(): \ArrayIterator
+	{
+		return new \ArrayIterator($this->children);
+	}
+
+
+	/**
+	 * Returns all children.
+	 */
+	final public function getChildren(): array
+	{
+		return $this->children;
+	}
+
+
+	/**
+	 * Renders element's start tag, content and end tag.
+	 */
+	final public function render(int $indent = null): string
+	{
+		$s = $this->startTag();
+
+		if (!$this->isEmpty) {
+			// add content
+			if ($indent !== null) {
+				$indent++;
+			}
+			foreach ($this->children as $child) {
+				if ($child instanceof self) {
+					$s .= $child->render($indent);
+				} else {
+					$s .= $child;
+				}
+			}
+
+			// add end tag
+			$s .= $this->endTag();
+		}
+
+		if ($indent !== null) {
+			return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
+		}
+		return $s;
+	}
+
+
+	final public function __toString(): string
+	{
+		try {
+			return $this->render();
+		} catch (\Throwable $e) {
+			if (PHP_VERSION_ID >= 70400) {
+				throw $e;
+			}
+			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+			return '';
+		}
+	}
+
+
+	/**
+	 * Returns element's start tag.
+	 */
+	final public function startTag(): string
+	{
+		return $this->name
+			? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>')
+			: '';
+	}
+
+
+	/**
+	 * Returns element's end tag.
+	 */
+	final public function endTag(): string
+	{
+		return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
+	}
+
+
+	/**
+	 * Returns element's attributes.
+	 * @internal
+	 */
+	final public function attributes(): string
+	{
+		if (!is_array($this->attrs)) {
+			return '';
+		}
+
+		$s = '';
+		$attrs = $this->attrs;
+		foreach ($attrs as $key => $value) {
+			if ($value === null || $value === false) {
+				continue;
+
+			} elseif ($value === true) {
+				if (static::$xhtml) {
+					$s .= ' ' . $key . '="' . $key . '"';
+				} else {
+					$s .= ' ' . $key;
+				}
+				continue;
+
+			} elseif (is_array($value)) {
+				if (strncmp($key, 'data-', 5) === 0) {
+					$value = Json::encode($value);
+
+				} else {
+					$tmp = null;
+					foreach ($value as $k => $v) {
+						if ($v != null) { // intentionally ==, skip nulls & empty string
+							// composite 'style' vs. 'others'
+							$tmp[] = $v === true
+								? $k
+								: (is_string($k) ? $k . ':' . $v : $v);
+						}
+					}
+					if ($tmp === null) {
+						continue;
+					}
+
+					$value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
+				}
+
+			} elseif (is_float($value)) {
+				$value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
+
+			} else {
+				$value = (string) $value;
+			}
+
+			$q = strpos($value, '"') === false ? '"' : "'";
+			$s .= ' ' . $key . '=' . $q
+				. str_replace(
+					['&', $q, '<'],
+					['&amp;', $q === '"' ? '&quot;' : '&#39;', self::$xhtml ? '&lt;' : '<'],
+					$value
+				)
+				. (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
+				. $q;
+		}
+
+		$s = str_replace('@', '&#64;', $s);
+		return $s;
+	}
+
+
+	/**
+	 * Clones all children too.
+	 */
+	public function __clone()
+	{
+		foreach ($this->children as $key => $value) {
+			if (is_object($value)) {
+				$this->children[$key] = clone $value;
+			}
+		}
+	}
+}

+ 726 - 0
vendor/nette/utils/src/Utils/Image.php

@@ -0,0 +1,726 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Basic manipulation with images. Supported types are JPEG, PNG, GIF, WEBP and BMP.
+ *
+ * <code>
+ * $image = Image::fromFile('nette.jpg');
+ * $image->resize(150, 100);
+ * $image->sharpen();
+ * $image->send();
+ * </code>
+ *
+ * @method Image affine(array $affine, array $clip = null)
+ * @method array affineMatrixConcat(array $m1, array $m2)
+ * @method array affineMatrixGet(int $type, mixed $options = null)
+ * @method void alphaBlending(bool $on)
+ * @method void antialias(bool $on)
+ * @method void arc($x, $y, $w, $h, $start, $end, $color)
+ * @method void char(int $font, $x, $y, string $char, $color)
+ * @method void charUp(int $font, $x, $y, string $char, $color)
+ * @method int colorAllocate($red, $green, $blue)
+ * @method int colorAllocateAlpha($red, $green, $blue, $alpha)
+ * @method int colorAt($x, $y)
+ * @method int colorClosest($red, $green, $blue)
+ * @method int colorClosestAlpha($red, $green, $blue, $alpha)
+ * @method int colorClosestHWB($red, $green, $blue)
+ * @method void colorDeallocate($color)
+ * @method int colorExact($red, $green, $blue)
+ * @method int colorExactAlpha($red, $green, $blue, $alpha)
+ * @method void colorMatch(Image $image2)
+ * @method int colorResolve($red, $green, $blue)
+ * @method int colorResolveAlpha($red, $green, $blue, $alpha)
+ * @method void colorSet($index, $red, $green, $blue)
+ * @method array colorsForIndex($index)
+ * @method int colorsTotal()
+ * @method int colorTransparent($color = null)
+ * @method void convolution(array $matrix, float $div, float $offset)
+ * @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH)
+ * @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
+ * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
+ * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
+ * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
+ * @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1)
+ * @method void ellipse($cx, $cy, $w, $h, $color)
+ * @method void fill($x, $y, $color)
+ * @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
+ * @method void filledEllipse($cx, $cy, $w, $h, $color)
+ * @method void filledPolygon(array $points, $numPoints, $color)
+ * @method void filledRectangle($x1, $y1, $x2, $y2, $color)
+ * @method void fillToBorder($x, $y, $border, $color)
+ * @method void filter($filtertype)
+ * @method void flip(int $mode)
+ * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null)
+ * @method void gammaCorrect(float $inputgamma, float $outputgamma)
+ * @method array getClip()
+ * @method int interlace($interlace = null)
+ * @method bool isTrueColor()
+ * @method void layerEffect($effect)
+ * @method void line($x1, $y1, $x2, $y2, $color)
+ * @method void openPolygon(array $points, int $num_points, int $color)
+ * @method void paletteCopy(Image $source)
+ * @method void paletteToTrueColor()
+ * @method void polygon(array $points, $numPoints, $color)
+ * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null)
+ * @method void rectangle($x1, $y1, $x2, $y2, $col)
+ * @method mixed resolution(int $res_x = null, int $res_y = null)
+ * @method Image rotate(float $angle, $backgroundColor)
+ * @method void saveAlpha(bool $saveflag)
+ * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
+ * @method void setBrush(Image $brush)
+ * @method void setClip(int $x1, int $y1, int $x2, int $y2)
+ * @method void setInterpolation(int $method = IMG_BILINEAR_FIXED)
+ * @method void setPixel($x, $y, $color)
+ * @method void setStyle(array $style)
+ * @method void setThickness($thickness)
+ * @method void setTile(Image $tile)
+ * @method void string($font, $x, $y, string $s, $col)
+ * @method void stringUp($font, $x, $y, string $s, $col)
+ * @method void trueColorToPalette(bool $dither, $ncolors)
+ * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
+ * @property-read int $width
+ * @property-read int $height
+ * @property-read resource|\GdImage $imageResource
+ */
+class Image
+{
+	use Nette\SmartObject;
+
+	/** {@link resize()} only shrinks images */
+	public const SHRINK_ONLY = 0b0001;
+
+	/** {@link resize()} will ignore aspect ratio */
+	public const STRETCH = 0b0010;
+
+	/** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */
+	public const FIT = 0b0000;
+
+	/** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */
+	public const FILL = 0b0100;
+
+	/** {@link resize()} fills given area exactly */
+	public const EXACT = 0b1000;
+
+	/** image types */
+	public const
+		JPEG = IMAGETYPE_JPEG,
+		PNG = IMAGETYPE_PNG,
+		GIF = IMAGETYPE_GIF,
+		WEBP = IMAGETYPE_WEBP,
+		BMP = IMAGETYPE_BMP;
+
+	public const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
+
+	private const FORMATS = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::BMP => 'bmp'];
+
+	/** @var resource|\GdImage */
+	private $image;
+
+
+	/**
+	 * Returns RGB color (0..255) and transparency (0..127).
+	 */
+	public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array
+	{
+		return [
+			'red' => max(0, min(255, $red)),
+			'green' => max(0, min(255, $green)),
+			'blue' => max(0, min(255, $blue)),
+			'alpha' => max(0, min(127, $transparency)),
+		];
+	}
+
+
+	/**
+	 * Reads an image from a file and returns its type in $type.
+	 * @throws Nette\NotSupportedException if gd extension is not loaded
+	 * @throws UnknownImageFileException if file not found or file type is not known
+	 * @return static
+	 */
+	public static function fromFile(string $file, int &$type = null)
+	{
+		if (!extension_loaded('gd')) {
+			throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
+		}
+
+		$type = self::detectTypeFromFile($file);
+		if (!$type) {
+			throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
+		}
+
+		$method = 'imagecreatefrom' . self::FORMATS[$type];
+		return new static(Callback::invokeSafe($method, [$file], function (string $message): void {
+			throw new ImageException($message);
+		}));
+	}
+
+
+	/**
+	 * Reads an image from a string and returns its type in $type.
+	 * @return static
+	 * @throws Nette\NotSupportedException if gd extension is not loaded
+	 * @throws ImageException
+	 */
+	public static function fromString(string $s, int &$type = null)
+	{
+		if (!extension_loaded('gd')) {
+			throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
+		}
+
+		$type = self::detectTypeFromString($s);
+		if (!$type) {
+			throw new UnknownImageFileException('Unknown type of image.');
+		}
+
+		return new static(Callback::invokeSafe('imagecreatefromstring', [$s], function (string $message): void {
+			throw new ImageException($message);
+		}));
+	}
+
+
+	/**
+	 * Creates a new true color image of the given dimensions. The default color is black.
+	 * @return static
+	 * @throws Nette\NotSupportedException if gd extension is not loaded
+	 */
+	public static function fromBlank(int $width, int $height, array $color = null)
+	{
+		if (!extension_loaded('gd')) {
+			throw new Nette\NotSupportedException('PHP extension GD is not loaded.');
+		}
+
+		if ($width < 1 || $height < 1) {
+			throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.');
+		}
+
+		$image = imagecreatetruecolor($width, $height);
+		if ($color) {
+			$color += ['alpha' => 0];
+			$color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
+			imagealphablending($image, false);
+			imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
+			imagealphablending($image, true);
+		}
+		return new static($image);
+	}
+
+
+	/**
+	 * Returns the type of image from file.
+	 */
+	public static function detectTypeFromFile(string $file): ?int
+	{
+		$type = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error
+		return isset(self::FORMATS[$type]) ? $type : null;
+	}
+
+
+	/**
+	 * Returns the type of image from string.
+	 */
+	public static function detectTypeFromString(string $s): ?int
+	{
+		$type = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error
+		return isset(self::FORMATS[$type]) ? $type : null;
+	}
+
+
+	/**
+	 * Returns the file extension for the given `Image::XXX` constant.
+	 */
+	public static function typeToExtension(int $type): string
+	{
+		if (!isset(self::FORMATS[$type])) {
+			throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
+		}
+		return self::FORMATS[$type];
+	}
+
+
+	/**
+	 * Returns the mime type for the given `Image::XXX` constant.
+	 */
+	public static function typeToMimeType(int $type): string
+	{
+		return 'image/' . self::typeToExtension($type);
+	}
+
+
+	/**
+	 * Wraps GD image.
+	 * @param  resource|\GdImage  $image
+	 */
+	public function __construct($image)
+	{
+		$this->setImageResource($image);
+		imagesavealpha($image, true);
+	}
+
+
+	/**
+	 * Returns image width.
+	 */
+	public function getWidth(): int
+	{
+		return imagesx($this->image);
+	}
+
+
+	/**
+	 * Returns image height.
+	 */
+	public function getHeight(): int
+	{
+		return imagesy($this->image);
+	}
+
+
+	/**
+	 * Sets image resource.
+	 * @param  resource|\GdImage  $image
+	 * @return static
+	 */
+	protected function setImageResource($image)
+	{
+		if (!$image instanceof \GdImage && !(is_resource($image) && get_resource_type($image) === 'gd')) {
+			throw new Nette\InvalidArgumentException('Image is not valid.');
+		}
+		$this->image = $image;
+		return $this;
+	}
+
+
+	/**
+	 * Returns image GD resource.
+	 * @return resource|\GdImage
+	 */
+	public function getImageResource()
+	{
+		return $this->image;
+	}
+
+
+	/**
+	 * Scales an image.
+	 * @param  int|string|null  $width in pixels or percent
+	 * @param  int|string|null  $height in pixels or percent
+	 * @return static
+	 */
+	public function resize($width, $height, int $flags = self::FIT)
+	{
+		if ($flags & self::EXACT) {
+			return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
+		}
+
+		[$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);
+
+		if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
+			$newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource();
+			imagecopyresampled(
+				$newImage,
+				$this->image,
+				0,
+				0,
+				0,
+				0,
+				$newWidth,
+				$newHeight,
+				$this->getWidth(),
+				$this->getHeight()
+			);
+			$this->image = $newImage;
+		}
+
+		if ($width < 0 || $height < 0) {
+			imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL);
+		}
+		return $this;
+	}
+
+
+	/**
+	 * Calculates dimensions of resized image.
+	 * @param  int|string|null  $newWidth in pixels or percent
+	 * @param  int|string|null  $newHeight in pixels or percent
+	 */
+	public static function calculateSize(
+		int $srcWidth,
+		int $srcHeight,
+		$newWidth,
+		$newHeight,
+		int $flags = self::FIT
+	): array {
+		if ($newWidth === null) {
+		} elseif (self::isPercent($newWidth)) {
+			$newWidth = (int) round($srcWidth / 100 * abs($newWidth));
+			$percents = true;
+		} else {
+			$newWidth = abs($newWidth);
+		}
+
+		if ($newHeight === null) {
+		} elseif (self::isPercent($newHeight)) {
+			$newHeight = (int) round($srcHeight / 100 * abs($newHeight));
+			$flags |= empty($percents) ? 0 : self::STRETCH;
+		} else {
+			$newHeight = abs($newHeight);
+		}
+
+		if ($flags & self::STRETCH) { // non-proportional
+			if (!$newWidth || !$newHeight) {
+				throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.');
+			}
+
+			if ($flags & self::SHRINK_ONLY) {
+				$newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth));
+				$newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight));
+			}
+
+		} else {  // proportional
+			if (!$newWidth && !$newHeight) {
+				throw new Nette\InvalidArgumentException('At least width or height must be specified.');
+			}
+
+			$scale = [];
+			if ($newWidth > 0) { // fit width
+				$scale[] = $newWidth / $srcWidth;
+			}
+
+			if ($newHeight > 0) { // fit height
+				$scale[] = $newHeight / $srcHeight;
+			}
+
+			if ($flags & self::FILL) {
+				$scale = [max($scale)];
+			}
+
+			if ($flags & self::SHRINK_ONLY) {
+				$scale[] = 1;
+			}
+
+			$scale = min($scale);
+			$newWidth = (int) round($srcWidth * $scale);
+			$newHeight = (int) round($srcHeight * $scale);
+		}
+
+		return [max($newWidth, 1), max($newHeight, 1)];
+	}
+
+
+	/**
+	 * Crops image.
+	 * @param  int|string  $left in pixels or percent
+	 * @param  int|string  $top in pixels or percent
+	 * @param  int|string  $width in pixels or percent
+	 * @param  int|string  $height in pixels or percent
+	 * @return static
+	 */
+	public function crop($left, $top, $width, $height)
+	{
+		[$r['x'], $r['y'], $r['width'], $r['height']]
+			= static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
+		if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') {
+			$this->image = imagecrop($this->image, $r);
+			imagesavealpha($this->image, true);
+		} else {
+			$newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource();
+			imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
+			$this->image = $newImage;
+		}
+		return $this;
+	}
+
+
+	/**
+	 * Calculates dimensions of cutout in image.
+	 * @param  int|string  $left in pixels or percent
+	 * @param  int|string  $top in pixels or percent
+	 * @param  int|string  $newWidth in pixels or percent
+	 * @param  int|string  $newHeight in pixels or percent
+	 */
+	public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array
+	{
+		if (self::isPercent($newWidth)) {
+			$newWidth = (int) round($srcWidth / 100 * $newWidth);
+		}
+		if (self::isPercent($newHeight)) {
+			$newHeight = (int) round($srcHeight / 100 * $newHeight);
+		}
+		if (self::isPercent($left)) {
+			$left = (int) round(($srcWidth - $newWidth) / 100 * $left);
+		}
+		if (self::isPercent($top)) {
+			$top = (int) round(($srcHeight - $newHeight) / 100 * $top);
+		}
+		if ($left < 0) {
+			$newWidth += $left;
+			$left = 0;
+		}
+		if ($top < 0) {
+			$newHeight += $top;
+			$top = 0;
+		}
+		$newWidth = min($newWidth, $srcWidth - $left);
+		$newHeight = min($newHeight, $srcHeight - $top);
+		return [$left, $top, $newWidth, $newHeight];
+	}
+
+
+	/**
+	 * Sharpens image a little bit.
+	 * @return static
+	 */
+	public function sharpen()
+	{
+		imageconvolution($this->image, [ // my magic numbers ;)
+			[-1, -1, -1],
+			[-1, 24, -1],
+			[-1, -1, -1],
+		], 16, 0);
+		return $this;
+	}
+
+
+	/**
+	 * Puts another image into this image.
+	 * @param  int|string  $left in pixels or percent
+	 * @param  int|string  $top in pixels or percent
+	 * @param  int  $opacity 0..100
+	 * @return static
+	 */
+	public function place(self $image, $left = 0, $top = 0, int $opacity = 100)
+	{
+		$opacity = max(0, min(100, $opacity));
+		if ($opacity === 0) {
+			return $this;
+		}
+
+		$width = $image->getWidth();
+		$height = $image->getHeight();
+
+		if (self::isPercent($left)) {
+			$left = (int) round(($this->getWidth() - $width) / 100 * $left);
+		}
+
+		if (self::isPercent($top)) {
+			$top = (int) round(($this->getHeight() - $height) / 100 * $top);
+		}
+
+		$output = $input = $image->image;
+		if ($opacity < 100) {
+			$tbl = [];
+			for ($i = 0; $i < 128; $i++) {
+				$tbl[$i] = round(127 - (127 - $i) * $opacity / 100);
+			}
+
+			$output = imagecreatetruecolor($width, $height);
+			imagealphablending($output, false);
+			if (!$image->isTrueColor()) {
+				$input = $output;
+				imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127));
+				imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height);
+			}
+			for ($x = 0; $x < $width; $x++) {
+				for ($y = 0; $y < $height; $y++) {
+					$c = \imagecolorat($input, $x, $y);
+					$c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24);
+					\imagesetpixel($output, $x, $y, $c);
+				}
+			}
+			imagealphablending($output, true);
+		}
+
+		imagecopy(
+			$this->image,
+			$output,
+			$left,
+			$top,
+			0,
+			0,
+			$width,
+			$height
+		);
+		return $this;
+	}
+
+
+	/**
+	 * Saves image to the file. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9).
+	 * @throws ImageException
+	 */
+	public function save(string $file, int $quality = null, int $type = null): void
+	{
+		if ($type === null) {
+			$extensions = array_flip(self::FORMATS) + ['jpg' => self::JPEG];
+			$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
+			if (!isset($extensions[$ext])) {
+				throw new Nette\InvalidArgumentException("Unsupported file extension '$ext'.");
+			}
+			$type = $extensions[$ext];
+		}
+
+		$this->output($type, $quality, $file);
+	}
+
+
+	/**
+	 * Outputs image to string. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9).
+	 */
+	public function toString(int $type = self::JPEG, int $quality = null): string
+	{
+		return Helpers::capture(function () use ($type, $quality) {
+			$this->output($type, $quality);
+		});
+	}
+
+
+	/**
+	 * Outputs image to string.
+	 */
+	public function __toString(): string
+	{
+		try {
+			return $this->toString();
+		} catch (\Throwable $e) {
+			if (func_num_args() || PHP_VERSION_ID >= 70400) {
+				throw $e;
+			}
+			trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+			return '';
+		}
+	}
+
+
+	/**
+	 * Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9).
+	 * @throws ImageException
+	 */
+	public function send(int $type = self::JPEG, int $quality = null): void
+	{
+		header('Content-Type: ' . self::typeToMimeType($type));
+		$this->output($type, $quality);
+	}
+
+
+	/**
+	 * Outputs image to browser or file.
+	 * @throws ImageException
+	 */
+	private function output(int $type, ?int $quality, string $file = null): void
+	{
+		switch ($type) {
+			case self::JPEG:
+				$quality = $quality === null ? 85 : max(0, min(100, $quality));
+				$success = @imagejpeg($this->image, $file, $quality); // @ is escalated to exception
+				break;
+
+			case self::PNG:
+				$quality = $quality === null ? 9 : max(0, min(9, $quality));
+				$success = @imagepng($this->image, $file, $quality); // @ is escalated to exception
+				break;
+
+			case self::GIF:
+				$success = @imagegif($this->image, $file); // @ is escalated to exception
+				break;
+
+			case self::WEBP:
+				$quality = $quality === null ? 80 : max(0, min(100, $quality));
+				$success = @imagewebp($this->image, $file, $quality); // @ is escalated to exception
+				break;
+
+			case self::BMP:
+				$success = @imagebmp($this->image, $file); // @ is escalated to exception
+				break;
+
+			default:
+				throw new Nette\InvalidArgumentException("Unsupported image type '$type'.");
+		}
+		if (!$success) {
+			throw new ImageException(Helpers::getLastError() ?: 'Unknown error');
+		}
+	}
+
+
+	/**
+	 * Call to undefined method.
+	 * @return mixed
+	 * @throws Nette\MemberAccessException
+	 */
+	public function __call(string $name, array $args)
+	{
+		$function = 'image' . $name;
+		if (!function_exists($function)) {
+			ObjectHelpers::strictCall(static::class, $name);
+		}
+
+		foreach ($args as $key => $value) {
+			if ($value instanceof self) {
+				$args[$key] = $value->getImageResource();
+
+			} elseif (is_array($value) && isset($value['red'])) { // rgb
+				$args[$key] = imagecolorallocatealpha(
+					$this->image,
+					$value['red'],
+					$value['green'],
+					$value['blue'],
+					$value['alpha']
+				) ?: imagecolorresolvealpha(
+					$this->image,
+					$value['red'],
+					$value['green'],
+					$value['blue'],
+					$value['alpha']
+				);
+			}
+		}
+		$res = $function($this->image, ...$args);
+		return $res instanceof \GdImage || (is_resource($res) && get_resource_type($res) === 'gd')
+			? $this->setImageResource($res)
+			: $res;
+	}
+
+
+	public function __clone()
+	{
+		ob_start(function () {});
+		imagegd2($this->image);
+		$this->setImageResource(imagecreatefromstring(ob_get_clean()));
+	}
+
+
+	/**
+	 * @param  int|string  $num in pixels or percent
+	 */
+	private static function isPercent(&$num): bool
+	{
+		if (is_string($num) && substr($num, -1) === '%') {
+			$num = (float) substr($num, 0, -1);
+			return true;
+		} elseif (is_int($num) || $num === (string) (int) $num) {
+			$num = (int) $num;
+			return false;
+		}
+		throw new Nette\InvalidArgumentException("Expected dimension in int|string, '$num' given.");
+	}
+
+
+	/**
+	 * Prevents serialization.
+	 */
+	public function __sleep(): array
+	{
+		throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.');
+	}
+}

+ 64 - 0
vendor/nette/utils/src/Utils/Json.php

@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * JSON encoder and decoder.
+ */
+final class Json
+{
+	use Nette\StaticClass;
+
+	public const FORCE_ARRAY = 0b0001;
+
+	public const PRETTY = 0b0010;
+
+	public const ESCAPE_UNICODE = 0b0100;
+
+
+	/**
+	 * Converts value to JSON format. The flag can be Json::PRETTY, which formats JSON for easier reading and clarity,
+	 * and Json::ESCAPE_UNICODE for ASCII output.
+	 * @param  mixed  $value
+	 * @throws JsonException
+	 */
+	public static function encode($value, int $flags = 0): string
+	{
+		$flags = ($flags & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE)
+			| JSON_UNESCAPED_SLASHES
+			| ($flags & self::PRETTY ? JSON_PRETTY_PRINT : 0)
+			| (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7
+
+		$json = json_encode($value, $flags);
+		if ($error = json_last_error()) {
+			throw new JsonException(json_last_error_msg(), $error);
+		}
+		return $json;
+	}
+
+
+	/**
+	 * Parses JSON to PHP value. The flag can be Json::FORCE_ARRAY, which forces an array instead of an object as the return value.
+	 * @return mixed
+	 * @throws JsonException
+	 */
+	public static function decode(string $json, int $flags = 0)
+	{
+		$forceArray = (bool) ($flags & self::FORCE_ARRAY);
+		$value = json_decode($json, $forceArray, 512, JSON_BIGINT_AS_STRING);
+		if ($error = json_last_error()) {
+			throw new JsonException(json_last_error_msg(), $error);
+		}
+		return $value;
+	}
+}

+ 212 - 0
vendor/nette/utils/src/Utils/ObjectHelpers.php

@@ -0,0 +1,212 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+use Nette\MemberAccessException;
+
+
+/**
+ * Nette\SmartObject helpers.
+ */
+final class ObjectHelpers
+{
+	use Nette\StaticClass;
+
+	/** @throws MemberAccessException */
+	public static function strictGet(string $class, string $name): void
+	{
+		$rc = new \ReflectionClass($class);
+		$hint = self::getSuggestion(array_merge(
+			array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
+			self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
+		), $name);
+		throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
+	}
+
+
+	/** @throws MemberAccessException */
+	public static function strictSet(string $class, string $name): void
+	{
+		$rc = new \ReflectionClass($class);
+		$hint = self::getSuggestion(array_merge(
+			array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
+			self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
+		), $name);
+		throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
+	}
+
+
+	/** @throws MemberAccessException */
+	public static function strictCall(string $class, string $method, array $additionalMethods = []): void
+	{
+		$trace = debug_backtrace(0, 3); // suppose this method is called from __call()
+		$context = ($trace[1]['function'] ?? null) === '__call'
+			? ($trace[2]['class'] ?? null)
+			: null;
+
+		if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
+			$class = get_parent_class($context);
+		}
+
+		if (method_exists($class, $method)) { // insufficient visibility
+			$rm = new \ReflectionMethod($class, $method);
+			$visibility = $rm->isPrivate()
+				? 'private '
+				: ($rm->isProtected() ? 'protected ' : '');
+			throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
+
+		} else {
+			$hint = self::getSuggestion(array_merge(
+				get_class_methods($class),
+				self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
+				$additionalMethods
+			), $method);
+			throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
+		}
+	}
+
+
+	/** @throws MemberAccessException */
+	public static function strictStaticCall(string $class, string $method): void
+	{
+		$trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic()
+		$context = ($trace[1]['function'] ?? null) === '__callStatic'
+			? ($trace[2]['class'] ?? null)
+			: null;
+
+		if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
+			$class = get_parent_class($context);
+		}
+
+		if (method_exists($class, $method)) { // insufficient visibility
+			$rm = new \ReflectionMethod($class, $method);
+			$visibility = $rm->isPrivate()
+				? 'private '
+				: ($rm->isProtected() ? 'protected ' : '');
+			throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
+
+		} else {
+			$hint = self::getSuggestion(
+				array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
+				$method
+			);
+			throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
+		}
+	}
+
+
+	/**
+	 * Returns array of magic properties defined by annotation @property.
+	 * @return array of [name => bit mask]
+	 * @internal
+	 */
+	public static function getMagicProperties(string $class): array
+	{
+		static $cache;
+		$props = &$cache[$class];
+		if ($props !== null) {
+			return $props;
+		}
+
+		$rc = new \ReflectionClass($class);
+		preg_match_all(
+			'~^  [ \t*]*  @property(|-read|-write)  [ \t]+  [^\s$]+  [ \t]+  \$  (\w+)  ()~mx',
+			(string) $rc->getDocComment(),
+			$matches,
+			PREG_SET_ORDER
+		);
+
+		$props = [];
+		foreach ($matches as [, $type, $name]) {
+			$uname = ucfirst($name);
+			$write = $type !== '-read'
+				&& $rc->hasMethod($nm = 'set' . $uname)
+				&& ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic();
+			$read = $type !== '-write'
+				&& ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
+				&& ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic();
+
+			if ($read || $write) {
+				$props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3;
+			}
+		}
+
+		foreach ($rc->getTraits() as $trait) {
+			$props += self::getMagicProperties($trait->name);
+		}
+
+		if ($parent = get_parent_class($class)) {
+			$props += self::getMagicProperties($parent);
+		}
+		return $props;
+	}
+
+
+	/**
+	 * Finds the best suggestion (for 8-bit encoding).
+	 * @param  (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[]  $possibilities
+	 * @internal
+	 */
+	public static function getSuggestion(array $possibilities, string $value): ?string
+	{
+		$norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value);
+		$best = null;
+		$min = (strlen($value) / 4 + 1) * 10 + .1;
+		foreach (array_unique($possibilities, SORT_REGULAR) as $item) {
+			$item = $item instanceof \Reflector ? $item->name : $item;
+			if ($item !== $value && (
+				($len = levenshtein($item, $value, 10, 11, 10)) < $min
+				|| ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min
+			)) {
+				$min = $len;
+				$best = $item;
+			}
+		}
+		return $best;
+	}
+
+
+	private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array
+	{
+		do {
+			$doc[] = $rc->getDocComment();
+			$traits = $rc->getTraits();
+			while ($trait = array_pop($traits)) {
+				$doc[] = $trait->getDocComment();
+				$traits += $trait->getTraits();
+			}
+		} while ($rc = $rc->getParentClass());
+		return preg_match_all($pattern, implode($doc), $m) ? $m[1] : [];
+	}
+
+
+	/**
+	 * Checks if the public non-static property exists.
+	 * @return bool|string returns 'event' if the property exists and has event like name
+	 * @internal
+	 */
+	public static function hasProperty(string $class, string $name)
+	{
+		static $cache;
+		$prop = &$cache[$class][$name];
+		if ($prop === null) {
+			$prop = false;
+			try {
+				$rp = new \ReflectionProperty($class, $name);
+				if ($rp->isPublic() && !$rp->isStatic()) {
+					$prop = $name >= 'onA' && $name < 'on_' ? 'event' : true;
+				}
+			} catch (\ReflectionException $e) {
+			}
+		}
+		return $prop;
+	}
+}

+ 41 - 0
vendor/nette/utils/src/Utils/ObjectMixin.php

@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Nette\Object behaviour mixin.
+ * @deprecated
+ */
+final class ObjectMixin
+{
+	use Nette\StaticClass;
+
+	/** @deprecated  use ObjectHelpers::getSuggestion() */
+	public static function getSuggestion(array $possibilities, string $value): ?string
+	{
+		trigger_error(__METHOD__ . '() has been renamed to Nette\Utils\ObjectHelpers::getSuggestion()', E_USER_DEPRECATED);
+		return ObjectHelpers::getSuggestion($possibilities, $value);
+	}
+
+
+	public static function setExtensionMethod(): void
+	{
+		trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
+	}
+
+
+	public static function getExtensionMethod(): void
+	{
+		trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
+	}
+}

+ 242 - 0
vendor/nette/utils/src/Utils/Paginator.php

@@ -0,0 +1,242 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Paginating math.
+ *
+ * @property   int $page
+ * @property-read int $firstPage
+ * @property-read int|null $lastPage
+ * @property-read int $firstItemOnPage
+ * @property-read int $lastItemOnPage
+ * @property   int $base
+ * @property-read bool $first
+ * @property-read bool $last
+ * @property-read int|null $pageCount
+ * @property   int $itemsPerPage
+ * @property   int|null $itemCount
+ * @property-read int $offset
+ * @property-read int|null $countdownOffset
+ * @property-read int $length
+ */
+class Paginator
+{
+	use Nette\SmartObject;
+
+	/** @var int */
+	private $base = 1;
+
+	/** @var int */
+	private $itemsPerPage = 1;
+
+	/** @var int */
+	private $page = 1;
+
+	/** @var int|null */
+	private $itemCount;
+
+
+	/**
+	 * Sets current page number.
+	 * @return static
+	 */
+	public function setPage(int $page)
+	{
+		$this->page = $page;
+		return $this;
+	}
+
+
+	/**
+	 * Returns current page number.
+	 */
+	public function getPage(): int
+	{
+		return $this->base + $this->getPageIndex();
+	}
+
+
+	/**
+	 * Returns first page number.
+	 */
+	public function getFirstPage(): int
+	{
+		return $this->base;
+	}
+
+
+	/**
+	 * Returns last page number.
+	 */
+	public function getLastPage(): ?int
+	{
+		return $this->itemCount === null
+			? null
+			: $this->base + max(0, $this->getPageCount() - 1);
+	}
+
+
+	/**
+	 * Returns the sequence number of the first element on the page
+	 */
+	public function getFirstItemOnPage(): int
+	{
+		return $this->itemCount !== 0
+			? $this->offset + 1
+			: 0;
+	}
+
+
+	/**
+	 * Returns the sequence number of the last element on the page
+	 */
+	public function getLastItemOnPage(): int
+	{
+		return $this->offset + $this->length;
+	}
+
+
+	/**
+	 * Sets first page (base) number.
+	 * @return static
+	 */
+	public function setBase(int $base)
+	{
+		$this->base = $base;
+		return $this;
+	}
+
+
+	/**
+	 * Returns first page (base) number.
+	 */
+	public function getBase(): int
+	{
+		return $this->base;
+	}
+
+
+	/**
+	 * Returns zero-based page number.
+	 */
+	protected function getPageIndex(): int
+	{
+		$index = max(0, $this->page - $this->base);
+		return $this->itemCount === null
+			? $index
+			: min($index, max(0, $this->getPageCount() - 1));
+	}
+
+
+	/**
+	 * Is the current page the first one?
+	 */
+	public function isFirst(): bool
+	{
+		return $this->getPageIndex() === 0;
+	}
+
+
+	/**
+	 * Is the current page the last one?
+	 */
+	public function isLast(): bool
+	{
+		return $this->itemCount === null
+			? false
+			: $this->getPageIndex() >= $this->getPageCount() - 1;
+	}
+
+
+	/**
+	 * Returns the total number of pages.
+	 */
+	public function getPageCount(): ?int
+	{
+		return $this->itemCount === null
+			? null
+			: (int) ceil($this->itemCount / $this->itemsPerPage);
+	}
+
+
+	/**
+	 * Sets the number of items to display on a single page.
+	 * @return static
+	 */
+	public function setItemsPerPage(int $itemsPerPage)
+	{
+		$this->itemsPerPage = max(1, $itemsPerPage);
+		return $this;
+	}
+
+
+	/**
+	 * Returns the number of items to display on a single page.
+	 */
+	public function getItemsPerPage(): int
+	{
+		return $this->itemsPerPage;
+	}
+
+
+	/**
+	 * Sets the total number of items.
+	 * @return static
+	 */
+	public function setItemCount(int $itemCount = null)
+	{
+		$this->itemCount = $itemCount === null ? null : max(0, $itemCount);
+		return $this;
+	}
+
+
+	/**
+	 * Returns the total number of items.
+	 */
+	public function getItemCount(): ?int
+	{
+		return $this->itemCount;
+	}
+
+
+	/**
+	 * Returns the absolute index of the first item on current page.
+	 */
+	public function getOffset(): int
+	{
+		return $this->getPageIndex() * $this->itemsPerPage;
+	}
+
+
+	/**
+	 * Returns the absolute index of the first item on current page in countdown paging.
+	 */
+	public function getCountdownOffset(): ?int
+	{
+		return $this->itemCount === null
+			? null
+			: max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage);
+	}
+
+
+	/**
+	 * Returns the number of items on current page.
+	 */
+	public function getLength(): int
+	{
+		return $this->itemCount === null
+			? $this->itemsPerPage
+			: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
+	}
+}

+ 45 - 0
vendor/nette/utils/src/Utils/Random.php

@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Secure random string generator.
+ */
+final class Random
+{
+	use Nette\StaticClass;
+
+	/**
+	 * Generates a random string of given length from characters specified in second argument.
+	 * Supports intervals, such as `0-9` or `A-Z`.
+	 */
+	public static function generate(int $length = 10, string $charlist = '0-9a-z'): string
+	{
+		$charlist = count_chars(preg_replace_callback('#.-.#', function (array $m): string {
+			return implode('', range($m[0][0], $m[0][2]));
+		}, $charlist), 3);
+		$chLen = strlen($charlist);
+
+		if ($length < 1) {
+			throw new Nette\InvalidArgumentException('Length must be greater than zero.');
+		} elseif ($chLen < 2) {
+			throw new Nette\InvalidArgumentException('Character list must contain at least two chars.');
+		}
+
+		$res = '';
+		for ($i = 0; $i < $length; $i++) {
+			$res .= $charlist[random_int(0, $chLen - 1)];
+		}
+		return $res;
+	}
+}

+ 417 - 0
vendor/nette/utils/src/Utils/Reflection.php

@@ -0,0 +1,417 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * PHP reflection helpers.
+ */
+final class Reflection
+{
+	use Nette\StaticClass;
+
+	private const BUILTIN_TYPES = [
+		'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
+		'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1,
+	];
+
+
+	/**
+	 * Determines if type is PHP built-in type. Otherwise, it is the class name.
+	 */
+	public static function isBuiltinType(string $type): bool
+	{
+		return isset(self::BUILTIN_TYPES[strtolower($type)]);
+	}
+
+
+	/**
+	 * Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
+	 * If the function does not have a return type, it returns null.
+	 * If the function has union type, it throws Nette\InvalidStateException.
+	 */
+	public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
+	{
+		return self::getType($func, $func->getReturnType());
+	}
+
+
+	/**
+	 * Returns the types of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
+	 */
+	public static function getReturnTypes(\ReflectionFunctionAbstract $func): array
+	{
+		return self::getType($func, $func->getReturnType(), true);
+	}
+
+
+	/**
+	 * Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
+	 * If the parameter does not have a type, it returns null.
+	 * If the parameter has union type, it throws Nette\InvalidStateException.
+	 */
+	public static function getParameterType(\ReflectionParameter $param): ?string
+	{
+		return self::getType($param, $param->getType());
+	}
+
+
+	/**
+	 * Returns the types of given parameter and normalizes `self` and `parent` to the actual class names.
+	 */
+	public static function getParameterTypes(\ReflectionParameter $param): array
+	{
+		return self::getType($param, $param->getType(), true);
+	}
+
+
+	/**
+	 * Returns the type of given property and normalizes `self` and `parent` to the actual class names.
+	 * If the property does not have a type, it returns null.
+	 * If the property has union type, it throws Nette\InvalidStateException.
+	 */
+	public static function getPropertyType(\ReflectionProperty $prop): ?string
+	{
+		return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null);
+	}
+
+
+	/**
+	 * Returns the types of given property and normalizes `self` and `parent` to the actual class names.
+	 */
+	public static function getPropertyTypes(\ReflectionProperty $prop): array
+	{
+		return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null, true);
+	}
+
+
+	/**
+	 * @param  \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty  $reflection
+	 * @return string|array|null
+	 */
+	private static function getType($reflection, ?\ReflectionType $type, bool $asArray = false)
+	{
+		if ($type === null) {
+			return $asArray ? [] : null;
+
+		} elseif ($type instanceof \ReflectionNamedType) {
+			$name = self::normalizeType($type->getName(), $reflection);
+			if ($asArray) {
+				return $type->allowsNull() && $type->getName() !== 'mixed'
+					? [$name, 'null']
+					: [$name];
+			}
+			return $name;
+
+		} elseif ($type instanceof \ReflectionUnionType) {
+			if ($asArray) {
+				$types = [];
+				foreach ($type->getTypes() as $type) {
+					$types[] = self::normalizeType($type->getName(), $reflection);
+				}
+				return $types;
+			}
+			throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union type.');
+
+		} else {
+			throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
+		}
+	}
+
+
+	/**
+	 * @param  \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty  $reflection
+	 */
+	private static function normalizeType(string $type, $reflection): string
+	{
+		$lower = strtolower($type);
+		if ($reflection instanceof \ReflectionFunction) {
+			return $type;
+		} elseif ($lower === 'self' || $lower === 'static') {
+			return $reflection->getDeclaringClass()->name;
+		} elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
+			return $reflection->getDeclaringClass()->getParentClass()->name;
+		} else {
+			return $type;
+		}
+	}
+
+
+	/**
+	 * Returns the default value of parameter. If it is a constant, it returns its value.
+	 * @return mixed
+	 * @throws \ReflectionException  If the parameter does not have a default value or the constant cannot be resolved
+	 */
+	public static function getParameterDefaultValue(\ReflectionParameter $param)
+	{
+		if ($param->isDefaultValueConstant()) {
+			$const = $orig = $param->getDefaultValueConstantName();
+			$pair = explode('::', $const);
+			if (isset($pair[1])) {
+				$pair[0] = self::normalizeType($pair[0], $param);
+				try {
+					$rcc = new \ReflectionClassConstant($pair[0], $pair[1]);
+				} catch (\ReflectionException $e) {
+					$name = self::toString($param);
+					throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e);
+				}
+				return $rcc->getValue();
+
+			} elseif (!defined($const)) {
+				$const = substr((string) strrchr($const, '\\'), 1);
+				if (!defined($const)) {
+					$name = self::toString($param);
+					throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.");
+				}
+			}
+			return constant($const);
+		}
+		return $param->getDefaultValue();
+	}
+
+
+	/**
+	 * Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait.
+	 */
+	public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
+	{
+		foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
+			if ($trait->hasProperty($prop->name)
+				// doc-comment guessing as workaround for insufficient PHP reflection
+				&& $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment()
+			) {
+				return self::getPropertyDeclaringClass($trait->getProperty($prop->name));
+			}
+		}
+		return $prop->getDeclaringClass();
+	}
+
+
+	/**
+	 * Returns a reflection of a method that contains a declaration of $method.
+	 * Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name.
+	 */
+	public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod
+	{
+		// file & line guessing as workaround for insufficient PHP reflection
+		$decl = $method->getDeclaringClass();
+		if ($decl->getFileName() === $method->getFileName()
+			&& $decl->getStartLine() <= $method->getStartLine()
+			&& $decl->getEndLine() >= $method->getEndLine()
+		) {
+			return $method;
+		}
+
+		$hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()];
+		if (($alias = $decl->getTraitAliases()[$method->name] ?? null)
+			&& ($m = new \ReflectionMethod($alias))
+			&& $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]
+		) {
+			return self::getMethodDeclaringMethod($m);
+		}
+
+		foreach ($decl->getTraits() as $trait) {
+			if ($trait->hasMethod($method->name)
+				&& ($m = $trait->getMethod($method->name))
+				&& $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]
+			) {
+				return self::getMethodDeclaringMethod($m);
+			}
+		}
+		return $method;
+	}
+
+
+	/**
+	 * Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache.
+	 */
+	public static function areCommentsAvailable(): bool
+	{
+		static $res;
+		return $res ?? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment();
+	}
+
+
+	public static function toString(\Reflector $ref): string
+	{
+		if ($ref instanceof \ReflectionClass) {
+			return $ref->name;
+		} elseif ($ref instanceof \ReflectionMethod) {
+			return $ref->getDeclaringClass()->name . '::' . $ref->name . '()';
+		} elseif ($ref instanceof \ReflectionFunction) {
+			return $ref->name . '()';
+		} elseif ($ref instanceof \ReflectionProperty) {
+			return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name;
+		} elseif ($ref instanceof \ReflectionParameter) {
+			return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction());
+		} else {
+			throw new Nette\InvalidArgumentException;
+		}
+	}
+
+
+	/**
+	 * Expands the name of the class to full name in the given context of given class.
+	 * Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context.
+	 * @throws Nette\InvalidArgumentException
+	 */
+	public static function expandClassName(string $name, \ReflectionClass $context): string
+	{
+		$lower = strtolower($name);
+		if (empty($name)) {
+			throw new Nette\InvalidArgumentException('Class name must not be empty.');
+
+		} elseif (isset(self::BUILTIN_TYPES[$lower])) {
+			return $lower;
+
+		} elseif ($lower === 'self' || $lower === 'static') {
+			return $context->name;
+
+		} elseif ($name[0] === '\\') { // fully qualified name
+			return ltrim($name, '\\');
+		}
+
+		$uses = self::getUseStatements($context);
+		$parts = explode('\\', $name, 2);
+		if (isset($uses[$parts[0]])) {
+			$parts[0] = $uses[$parts[0]];
+			return implode('\\', $parts);
+
+		} elseif ($context->inNamespace()) {
+			return $context->getNamespaceName() . '\\' . $name;
+
+		} else {
+			return $name;
+		}
+	}
+
+
+	/** @return array of [alias => class] */
+	public static function getUseStatements(\ReflectionClass $class): array
+	{
+		if ($class->isAnonymous()) {
+			throw new Nette\NotImplementedException('Anonymous classes are not supported.');
+		}
+		static $cache = [];
+		if (!isset($cache[$name = $class->name])) {
+			if ($class->isInternal()) {
+				$cache[$name] = [];
+			} else {
+				$code = file_get_contents($class->getFileName());
+				$cache = self::parseUseStatements($code, $name) + $cache;
+			}
+		}
+		return $cache[$name];
+	}
+
+
+	/**
+	 * Parses PHP code to [class => [alias => class, ...]]
+	 */
+	private static function parseUseStatements(string $code, string $forClass = null): array
+	{
+		try {
+			$tokens = token_get_all($code, TOKEN_PARSE);
+		} catch (\ParseError $e) {
+			trigger_error($e->getMessage(), E_USER_NOTICE);
+			$tokens = [];
+		}
+		$namespace = $class = $classLevel = $level = null;
+		$res = $uses = [];
+
+		$nameTokens = PHP_VERSION_ID < 80000
+			? [T_STRING, T_NS_SEPARATOR]
+			: [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED];
+
+		while ($token = current($tokens)) {
+			next($tokens);
+			switch (is_array($token) ? $token[0] : $token) {
+				case T_NAMESPACE:
+					$namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\');
+					$uses = [];
+					break;
+
+				case T_CLASS:
+				case T_INTERFACE:
+				case T_TRAIT:
+					if ($name = self::fetch($tokens, T_STRING)) {
+						$class = $namespace . $name;
+						$classLevel = $level + 1;
+						$res[$class] = $uses;
+						if ($class === $forClass) {
+							return $res;
+						}
+					}
+					break;
+
+				case T_USE:
+					while (!$class && ($name = self::fetch($tokens, $nameTokens))) {
+						$name = ltrim($name, '\\');
+						if (self::fetch($tokens, '{')) {
+							while ($suffix = self::fetch($tokens, $nameTokens)) {
+								if (self::fetch($tokens, T_AS)) {
+									$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
+								} else {
+									$tmp = explode('\\', $suffix);
+									$uses[end($tmp)] = $name . $suffix;
+								}
+								if (!self::fetch($tokens, ',')) {
+									break;
+								}
+							}
+
+						} elseif (self::fetch($tokens, T_AS)) {
+							$uses[self::fetch($tokens, T_STRING)] = $name;
+
+						} else {
+							$tmp = explode('\\', $name);
+							$uses[end($tmp)] = $name;
+						}
+						if (!self::fetch($tokens, ',')) {
+							break;
+						}
+					}
+					break;
+
+				case T_CURLY_OPEN:
+				case T_DOLLAR_OPEN_CURLY_BRACES:
+				case '{':
+					$level++;
+					break;
+
+				case '}':
+					if ($level === $classLevel) {
+						$class = $classLevel = null;
+					}
+					$level--;
+			}
+		}
+
+		return $res;
+	}
+
+
+	private static function fetch(array &$tokens, $take): ?string
+	{
+		$res = null;
+		while ($token = current($tokens)) {
+			[$token, $s] = is_array($token) ? $token : [$token, $token];
+			if (in_array($token, (array) $take, true)) {
+				$res .= $s;
+			} elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
+				break;
+			}
+			next($tokens);
+		}
+		return $res;
+	}
+}

+ 550 - 0
vendor/nette/utils/src/Utils/Strings.php

@@ -0,0 +1,550 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+use function is_array, is_object, strlen;
+
+
+/**
+ * String tools library.
+ */
+class Strings
+{
+	use Nette\StaticClass;
+
+	public const TRIM_CHARACTERS = " \t\n\r\0\x0B\u{A0}";
+
+
+	/**
+	 * Checks if the string is valid in UTF-8 encoding.
+	 */
+	public static function checkEncoding(string $s): bool
+	{
+		return $s === self::fixEncoding($s);
+	}
+
+
+	/**
+	 * Removes all invalid UTF-8 characters from a string.
+	 */
+	public static function fixEncoding(string $s): string
+	{
+		// removes xD800-xDFFF, x110000 and higher
+		return htmlspecialchars_decode(htmlspecialchars($s, ENT_NOQUOTES | ENT_IGNORE, 'UTF-8'), ENT_NOQUOTES);
+	}
+
+
+	/**
+	 * Returns a specific character in UTF-8 from code point (number in range 0x0000..D7FF or 0xE000..10FFFF).
+	 * @throws Nette\InvalidArgumentException if code point is not in valid range
+	 */
+	public static function chr(int $code): string
+	{
+		if ($code < 0 || ($code >= 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) {
+			throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.');
+		} elseif (!extension_loaded('iconv')) {
+			throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
+		}
+		return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
+	}
+
+
+	/**
+	 * Starts the $haystack string with the prefix $needle?
+	 */
+	public static function startsWith(string $haystack, string $needle): bool
+	{
+		return strncmp($haystack, $needle, strlen($needle)) === 0;
+	}
+
+
+	/**
+	 * Ends the $haystack string with the suffix $needle?
+	 */
+	public static function endsWith(string $haystack, string $needle): bool
+	{
+		return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
+	}
+
+
+	/**
+	 * Does $haystack contain $needle?
+	 */
+	public static function contains(string $haystack, string $needle): bool
+	{
+		return strpos($haystack, $needle) !== false;
+	}
+
+
+	/**
+	 * Returns a part of UTF-8 string specified by starting position and length. If start is negative,
+	 * the returned string will start at the start'th character from the end of string.
+	 */
+	public static function substring(string $s, int $start, int $length = null): string
+	{
+		if (function_exists('mb_substr')) {
+			return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster
+		} elseif (!extension_loaded('iconv')) {
+			throw new Nette\NotSupportedException(__METHOD__ . '() requires extension ICONV or MBSTRING, neither is loaded.');
+		} elseif ($length === null) {
+			$length = self::length($s);
+		} elseif ($start < 0 && $length < 0) {
+			$start += self::length($s); // unifies iconv_substr behavior with mb_substr
+		}
+		return iconv_substr($s, $start, $length, 'UTF-8');
+	}
+
+
+	/**
+	 * Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines,
+	 * trims end spaces on lines, normalizes UTF-8 to the normal form of NFC.
+	 */
+	public static function normalize(string $s): string
+	{
+		// convert to compressed normal form (NFC)
+		if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) {
+			$s = $n;
+		}
+
+		$s = self::normalizeNewLines($s);
+
+		// remove control characters; leave \t + \n
+		$s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]);
+
+		// right trim
+		$s = self::pcre('preg_replace', ['#[\t ]+$#m', '', $s]);
+
+		// leading and trailing blank lines
+		$s = trim($s, "\n");
+
+		return $s;
+	}
+
+
+	/**
+	 * Standardize line endings to unix-like.
+	 */
+	public static function normalizeNewLines(string $s): string
+	{
+		return str_replace(["\r\n", "\r"], "\n", $s);
+	}
+
+
+	/**
+	 * Converts UTF-8 string to ASCII, ie removes diacritics etc.
+	 */
+	public static function toAscii(string $s): string
+	{
+		$iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null;
+		static $transliterator = null;
+		if ($transliterator === null) {
+			if (class_exists('Transliterator', false)) {
+				$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
+			} else {
+				trigger_error(__METHOD__ . "(): it is recommended to enable PHP extensions 'intl'.", E_USER_NOTICE);
+				$transliterator = false;
+			}
+		}
+
+		// remove control characters and check UTF-8 validity
+		$s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]);
+
+		// transliteration (by Transliterator and iconv) is not optimal, replace some characters directly
+		$s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß
+		if ($iconv !== 'libiconv') {
+			$s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔
+		}
+
+		if ($transliterator) {
+			$s = $transliterator->transliterate($s);
+			// use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ
+			if ($iconv === 'glibc') {
+				$s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates
+				$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
+				$s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters
+			} elseif ($iconv === 'libiconv') {
+				$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
+			} else { // null or 'unknown' (#216)
+				$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars
+			}
+		} elseif ($iconv === 'glibc' || $iconv === 'libiconv') {
+			// temporarily hide these characters to distinguish them from the garbage that iconv creates
+			$s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06");
+			if ($iconv === 'glibc') {
+				// glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved
+				$s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s);
+				$s = strtr(
+					$s,
+					"\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7",
+					'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.'
+				);
+				$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]);
+			} else {
+				$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
+			}
+			// remove garbage that iconv creates during transliteration (eg Ý -> Y')
+			$s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s);
+			// restore temporarily hidden characters
+			$s = strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?');
+		} else {
+			$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars
+		}
+
+		return $s;
+	}
+
+
+	/**
+	 * Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters
+	 * except letters of the English alphabet and numbers with a hyphens.
+	 */
+	public static function webalize(string $s, string $charlist = null, bool $lower = true): string
+	{
+		$s = self::toAscii($s);
+		if ($lower) {
+			$s = strtolower($s);
+		}
+		$s = self::pcre('preg_replace', ['#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s]);
+		$s = trim($s, '-');
+		return $s;
+	}
+
+
+	/**
+	 * Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated,
+	 * an ellipsis (or something else set with third argument) is appended to the string.
+	 */
+	public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string
+	{
+		if (self::length($s) > $maxLen) {
+			$maxLen -= self::length($append);
+			if ($maxLen < 1) {
+				return $append;
+
+			} elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) {
+				return $matches[0] . $append;
+
+			} else {
+				return self::substring($s, 0, $maxLen) . $append;
+			}
+		}
+		return $s;
+	}
+
+
+	/**
+	 * Indents a multiline text from the left. Second argument sets how many indentation chars should be used,
+	 * while the indent itself is the third argument (*tab* by default).
+	 */
+	public static function indent(string $s, int $level = 1, string $chars = "\t"): string
+	{
+		if ($level > 0) {
+			$s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level));
+		}
+		return $s;
+	}
+
+
+	/**
+	 * Converts all characters of UTF-8 string to lower case.
+	 */
+	public static function lower(string $s): string
+	{
+		return mb_strtolower($s, 'UTF-8');
+	}
+
+
+	/**
+	 * Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged.
+	 */
+	public static function firstLower(string $s): string
+	{
+		return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1);
+	}
+
+
+	/**
+	 * Converts all characters of a UTF-8 string to upper case.
+	 */
+	public static function upper(string $s): string
+	{
+		return mb_strtoupper($s, 'UTF-8');
+	}
+
+
+	/**
+	 * Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged.
+	 */
+	public static function firstUpper(string $s): string
+	{
+		return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1);
+	}
+
+
+	/**
+	 * Converts the first character of every word of a UTF-8 string to upper case and the others to lower case.
+	 */
+	public static function capitalize(string $s): string
+	{
+		return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
+	}
+
+
+	/**
+	 * Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared,
+	 * if it is negative, the corresponding number of characters from the end of the strings is compared,
+	 * otherwise the appropriate number of characters from the beginning is compared.
+	 */
+	public static function compare(string $left, string $right, int $length = null): bool
+	{
+		if (class_exists('Normalizer', false)) {
+			$left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster
+			$right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster
+		}
+
+		if ($length < 0) {
+			$left = self::substring($left, $length, -$length);
+			$right = self::substring($right, $length, -$length);
+		} elseif ($length !== null) {
+			$left = self::substring($left, 0, $length);
+			$right = self::substring($right, 0, $length);
+		}
+		return self::lower($left) === self::lower($right);
+	}
+
+
+	/**
+	 * Finds the common prefix of strings or returns empty string if the prefix was not found.
+	 * @param  string[]  $strings
+	 */
+	public static function findPrefix(array $strings): string
+	{
+		$first = array_shift($strings);
+		for ($i = 0; $i < strlen($first); $i++) {
+			foreach ($strings as $s) {
+				if (!isset($s[$i]) || $first[$i] !== $s[$i]) {
+					while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") {
+						$i--;
+					}
+					return substr($first, 0, $i);
+				}
+			}
+		}
+		return $first;
+	}
+
+
+	/**
+	 * Returns number of characters (not bytes) in UTF-8 string.
+	 * That is the number of Unicode code points which may differ from the number of graphemes.
+	 */
+	public static function length(string $s): int
+	{
+		return function_exists('mb_strlen')
+			? mb_strlen($s, 'UTF-8')
+			: strlen(utf8_decode($s));
+	}
+
+
+	/**
+	 * Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string.
+	 */
+	public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string
+	{
+		$charlist = preg_quote($charlist, '#');
+		return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', '');
+	}
+
+
+	/**
+	 * Pads a UTF-8 string to given length by prepending the $pad string to the beginning.
+	 */
+	public static function padLeft(string $s, int $length, string $pad = ' '): string
+	{
+		$length = max(0, $length - self::length($s));
+		$padLen = self::length($pad);
+		return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s;
+	}
+
+
+	/**
+	 * Pads UTF-8 string to given length by appending the $pad string to the end.
+	 */
+	public static function padRight(string $s, int $length, string $pad = ' '): string
+	{
+		$length = max(0, $length - self::length($s));
+		$padLen = self::length($pad);
+		return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen);
+	}
+
+
+	/**
+	 * Reverses UTF-8 string.
+	 */
+	public static function reverse(string $s): string
+	{
+		if (!extension_loaded('iconv')) {
+			throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
+		}
+		return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s)));
+	}
+
+
+	/**
+	 * Returns part of $haystack before $nth occurence of $needle or returns null if the needle was not found.
+	 * Negative value means searching from the end.
+	 */
+	public static function before(string $haystack, string $needle, int $nth = 1): ?string
+	{
+		$pos = self::pos($haystack, $needle, $nth);
+		return $pos === null
+			? null
+			: substr($haystack, 0, $pos);
+	}
+
+
+	/**
+	 * Returns part of $haystack after $nth occurence of $needle or returns null if the needle was not found.
+	 * Negative value means searching from the end.
+	 */
+	public static function after(string $haystack, string $needle, int $nth = 1): ?string
+	{
+		$pos = self::pos($haystack, $needle, $nth);
+		return $pos === null
+			? null
+			: substr($haystack, $pos + strlen($needle));
+	}
+
+
+	/**
+	 * Returns position in characters of $nth occurence of $needle in $haystack or null if the $needle was not found.
+	 * Negative value of `$nth` means searching from the end.
+	 */
+	public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int
+	{
+		$pos = self::pos($haystack, $needle, $nth);
+		return $pos === null
+			? null
+			: self::length(substr($haystack, 0, $pos));
+	}
+
+
+	/**
+	 * Returns position in characters of $nth occurence of $needle in $haystack or null if the needle was not found.
+	 */
+	private static function pos(string $haystack, string $needle, int $nth = 1): ?int
+	{
+		if (!$nth) {
+			return null;
+		} elseif ($nth > 0) {
+			if ($needle === '') {
+				return 0;
+			}
+			$pos = 0;
+			while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) {
+				$pos++;
+			}
+		} else {
+			$len = strlen($haystack);
+			if ($needle === '') {
+				return $len;
+			}
+			$pos = $len - 1;
+			while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) {
+				$pos--;
+			}
+		}
+		return Helpers::falseToNull($pos);
+	}
+
+
+	/**
+	 * Splits a string into array by the regular expression.
+	 * Argument $flag takes same arguments as preg_split(), but PREG_SPLIT_DELIM_CAPTURE is set by default.
+	 */
+	public static function split(string $subject, string $pattern, int $flags = 0): array
+	{
+		return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]);
+	}
+
+
+	/**
+	 * Checks if given string matches a regular expression pattern and returns an array with first found match and each subpattern.
+	 * Argument $flag takes same arguments as function preg_match().
+	 */
+	public static function match(string $subject, string $pattern, int $flags = 0, int $offset = 0): ?array
+	{
+		if ($offset > strlen($subject)) {
+			return null;
+		}
+		return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])
+			? $m
+			: null;
+	}
+
+
+	/**
+	 * Finds all occurrences matching regular expression pattern and returns a two-dimensional array.
+	 * Argument $flag takes same arguments as function preg_match_all(), but PREG_SET_ORDER is set by default.
+	 */
+	public static function matchAll(string $subject, string $pattern, int $flags = 0, int $offset = 0): array
+	{
+		if ($offset > strlen($subject)) {
+			return [];
+		}
+		self::pcre('preg_match_all', [
+			$pattern, $subject, &$m,
+			($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
+			$offset,
+		]);
+		return $m;
+	}
+
+
+	/**
+	 * Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`.
+	 * @param  string|array  $pattern
+	 * @param  string|callable  $replacement
+	 */
+	public static function replace(string $subject, $pattern, $replacement = '', int $limit = -1): string
+	{
+		if (is_object($replacement) || is_array($replacement)) {
+			if (!is_callable($replacement, false, $textual)) {
+				throw new Nette\InvalidStateException("Callback '$textual' is not callable.");
+			}
+			return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]);
+
+		} elseif (is_array($pattern) && is_string(key($pattern))) {
+			$replacement = array_values($pattern);
+			$pattern = array_keys($pattern);
+		}
+
+		return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]);
+	}
+
+
+	/** @internal */
+	public static function pcre(string $func, array $args)
+	{
+		$res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void {
+			// compile-time error, not detectable by preg_last_error
+			throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0]));
+		});
+
+		if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
+			&& ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true))
+		) {
+			throw new RegexpException((RegexpException::MESSAGES[$code] ?? 'Unknown error')
+				. ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
+		}
+		return $res;
+	}
+}

+ 373 - 0
vendor/nette/utils/src/Utils/Validators.php

@@ -0,0 +1,373 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+
+/**
+ * Validation utilities.
+ */
+class Validators
+{
+	use Nette\StaticClass;
+
+	/** @var array<string,?callable> */
+	protected static $validators = [
+		// PHP types
+		'array' => 'is_array',
+		'bool' => 'is_bool',
+		'boolean' => 'is_bool',
+		'float' => 'is_float',
+		'int' => 'is_int',
+		'integer' => 'is_int',
+		'null' => 'is_null',
+		'object' => 'is_object',
+		'resource' => 'is_resource',
+		'scalar' => 'is_scalar',
+		'string' => 'is_string',
+
+		// pseudo-types
+		'callable' => [self::class, 'isCallable'],
+		'iterable' => 'is_iterable',
+		'list' => [Arrays::class, 'isList'],
+		'mixed' => [self::class, 'isMixed'],
+		'none' => [self::class, 'isNone'],
+		'number' => [self::class, 'isNumber'],
+		'numeric' => [self::class, 'isNumeric'],
+		'numericint' => [self::class, 'isNumericInt'],
+
+		// string patterns
+		'alnum' => 'ctype_alnum',
+		'alpha' => 'ctype_alpha',
+		'digit' => 'ctype_digit',
+		'lower' => 'ctype_lower',
+		'pattern' => null,
+		'space' => 'ctype_space',
+		'unicode' => [self::class, 'isUnicode'],
+		'upper' => 'ctype_upper',
+		'xdigit' => 'ctype_xdigit',
+
+		// syntax validation
+		'email' => [self::class, 'isEmail'],
+		'identifier' => [self::class, 'isPhpIdentifier'],
+		'uri' => [self::class, 'isUri'],
+		'url' => [self::class, 'isUrl'],
+
+		// environment validation
+		'class' => 'class_exists',
+		'interface' => 'interface_exists',
+		'directory' => 'is_dir',
+		'file' => 'is_file',
+		'type' => [self::class, 'isType'],
+	];
+
+	/** @var array<string,callable> */
+	protected static $counters = [
+		'string' => 'strlen',
+		'unicode' => [Strings::class, 'length'],
+		'array' => 'count',
+		'list' => 'count',
+		'alnum' => 'strlen',
+		'alpha' => 'strlen',
+		'digit' => 'strlen',
+		'lower' => 'strlen',
+		'space' => 'strlen',
+		'upper' => 'strlen',
+		'xdigit' => 'strlen',
+	];
+
+
+	/**
+	 * Verifies that the value is of expected types separated by pipe.
+	 * @param  mixed  $value
+	 * @throws AssertionException
+	 */
+	public static function assert($value, string $expected, string $label = 'variable'): void
+	{
+		if (!static::is($value, $expected)) {
+			$expected = str_replace(['|', ':'], [' or ', ' in range '], $expected);
+			static $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null'];
+			$type = $translate[gettype($value)] ?? gettype($value);
+			if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) {
+				$type .= ' ' . var_export($value, true);
+			} elseif (is_object($value)) {
+				$type .= ' ' . get_class($value);
+			}
+			throw new AssertionException("The $label expects to be $expected, $type given.");
+		}
+	}
+
+
+	/**
+	 * Verifies that element $key in array is of expected types separated by pipe.
+	 * @param  mixed[]  $array
+	 * @param  int|string  $key
+	 * @throws AssertionException
+	 */
+	public static function assertField(
+		array $array,
+		$key,
+		string $expected = null,
+		string $label = "item '%' in array"
+	): void {
+		if (!array_key_exists($key, $array)) {
+			throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
+
+		} elseif ($expected) {
+			static::assert($array[$key], $expected, str_replace('%', $key, $label));
+		}
+	}
+
+
+	/**
+	 * Verifies that the value is of expected types separated by pipe.
+	 * @param  mixed  $value
+	 */
+	public static function is($value, string $expected): bool
+	{
+		foreach (explode('|', $expected) as $item) {
+			if (substr($item, -2) === '[]') {
+				if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) {
+					return true;
+				}
+				continue;
+			} elseif (substr($item, 0, 1) === '?') {
+				$item = substr($item, 1);
+				if ($value === null) {
+					return true;
+				}
+			}
+
+			[$type] = $item = explode(':', $item, 2);
+			if (isset(static::$validators[$type])) {
+				try {
+					if (!static::$validators[$type]($value)) {
+						continue;
+					}
+				} catch (\TypeError $e) {
+					continue;
+				}
+			} elseif ($type === 'pattern') {
+				if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) {
+					return true;
+				}
+				continue;
+			} elseif (!$value instanceof $type) {
+				continue;
+			}
+
+			if (isset($item[1])) {
+				$length = $value;
+				if (isset(static::$counters[$type])) {
+					$length = static::$counters[$type]($value);
+				}
+				$range = explode('..', $item[1]);
+				if (!isset($range[1])) {
+					$range[1] = $range[0];
+				}
+				if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) {
+					continue;
+				}
+			}
+			return true;
+		}
+		return false;
+	}
+
+
+	/**
+	 * Finds whether all values are of expected types separated by pipe.
+	 * @param  mixed[]  $values
+	 */
+	public static function everyIs(iterable $values, string $expected): bool
+	{
+		foreach ($values as $value) {
+			if (!static::is($value, $expected)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+
+	/**
+	 * Checks if the value is an integer or a float.
+	 * @param  mixed  $value
+	 */
+	public static function isNumber($value): bool
+	{
+		return is_int($value) || is_float($value);
+	}
+
+
+	/**
+	 * Checks if the value is an integer or a integer written in a string.
+	 * @param  mixed  $value
+	 */
+	public static function isNumericInt($value): bool
+	{
+		return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value));
+	}
+
+
+	/**
+	 * Checks if the value is a number or a number written in a string.
+	 * @param  mixed  $value
+	 */
+	public static function isNumeric($value): bool
+	{
+		return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]*[.]?[0-9]+$#D', $value));
+	}
+
+
+	/**
+	 * Checks if the value is a syntactically correct callback.
+	 * @param  mixed  $value
+	 */
+	public static function isCallable($value): bool
+	{
+		return $value && is_callable($value, true);
+	}
+
+
+	/**
+	 * Checks if the value is a valid UTF-8 string.
+	 * @param  mixed  $value
+	 */
+	public static function isUnicode($value): bool
+	{
+		return is_string($value) && preg_match('##u', $value);
+	}
+
+
+	/**
+	 * Checks if the value is 0, '', false or null.
+	 * @param  mixed  $value
+	 */
+	public static function isNone($value): bool
+	{
+		return $value == null; // intentionally ==
+	}
+
+
+	/** @internal */
+	public static function isMixed(): bool
+	{
+		return true;
+	}
+
+
+	/**
+	 * Checks if a variable is a zero-based integer indexed array.
+	 * @param  mixed  $value
+	 * @deprecated  use Nette\Utils\Arrays::isList
+	 */
+	public static function isList($value): bool
+	{
+		return Arrays::isList($value);
+	}
+
+
+	/**
+	 * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
+	 * Numbers, strings and DateTime objects can be compared.
+	 * @param  mixed  $value
+	 */
+	public static function isInRange($value, array $range): bool
+	{
+		if ($value === null || !(isset($range[0]) || isset($range[1]))) {
+			return false;
+		}
+		$limit = $range[0] ?? $range[1];
+		if (is_string($limit)) {
+			$value = (string) $value;
+		} elseif ($limit instanceof \DateTimeInterface) {
+			if (!$value instanceof \DateTimeInterface) {
+				return false;
+			}
+		} elseif (is_numeric($value)) {
+			$value *= 1;
+		} else {
+			return false;
+		}
+		return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1]));
+	}
+
+
+	/**
+	 * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified.
+	 */
+	public static function isEmail(string $value): bool
+	{
+		$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
+		$alpha = "a-z\x80-\xFF"; // superset of IDN
+		return (bool) preg_match(<<<XX
+		(^
+			("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*)  # quoted or unquoted
+			@
+			([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+  # domain - RFC 1034
+			[$alpha]([-0-9$alpha]{0,17}[$alpha])?              # top domain
+		$)Dix
+XX
+, $value);
+	}
+
+
+	/**
+	 * Checks if the value is a valid URL address.
+	 */
+	public static function isUrl(string $value): bool
+	{
+		$alpha = "a-z\x80-\xFF";
+		return (bool) preg_match(<<<XX
+		(^
+			https?://(
+				(([-_0-9$alpha]+\\.)*                       # subdomain
+					[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)?  # domain
+					[$alpha]([-0-9$alpha]{0,17}[$alpha])?   # top domain
+				|\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}  # IPv4
+				|\\[[0-9a-f:]{3,39}\\]                      # IPv6
+			)(:\\d{1,5})?                                   # port
+			(/\\S*)?                                        # path
+			(\\?\\S*)?                                      # query
+			(\\#\\S*)?                                      # fragment
+		$)Dix
+XX
+, $value);
+	}
+
+
+	/**
+	 * Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema.
+	 */
+	public static function isUri(string $value): bool
+	{
+		return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value);
+	}
+
+
+	/**
+	 * Checks whether the input is a class, interface or trait.
+	 */
+	public static function isType(string $type): bool
+	{
+		return class_exists($type) || interface_exists($type) || trait_exists($type);
+	}
+
+
+	/**
+	 * Checks whether the input is a valid PHP identifier.
+	 */
+	public static function isPhpIdentifier(string $value): bool
+	{
+		return is_string($value) && preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value);
+	}
+}

+ 58 - 0
vendor/nette/utils/src/Utils/exceptions.php

@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+
+/**
+ * The exception that is thrown when an image error occurs.
+ */
+class ImageException extends \Exception
+{
+}
+
+
+/**
+ * The exception that indicates invalid image file.
+ */
+class UnknownImageFileException extends ImageException
+{
+}
+
+
+/**
+ * The exception that indicates error of JSON encoding/decoding.
+ */
+class JsonException extends \Exception
+{
+}
+
+
+/**
+ * The exception that indicates error of the last Regexp execution.
+ */
+class RegexpException extends \Exception
+{
+	public const MESSAGES = [
+		PREG_INTERNAL_ERROR => 'Internal error',
+		PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted',
+		PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted',
+		PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
+		PREG_BAD_UTF8_OFFSET_ERROR => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point',
+		6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR
+	];
+}
+
+
+/**
+ * The exception that indicates assertion error.
+ */
+class AssertionException extends \Exception
+{
+}

+ 32 - 0
vendor/nette/utils/src/compatibility.php

@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette\Utils;
+
+use Nette;
+
+if (false) {
+	/** @deprecated use Nette\HtmlStringable */
+	interface IHtmlString extends Nette\HtmlStringable
+	{
+	}
+} elseif (!interface_exists(IHtmlString::class)) {
+	class_alias(Nette\HtmlStringable::class, IHtmlString::class);
+}
+
+namespace Nette\Localization;
+
+if (false) {
+	/** @deprecated use Nette\Localization\Translator */
+	interface ITranslator extends Translator
+	{
+	}
+} elseif (!interface_exists(ITranslator::class)) {
+	class_alias(Translator::class, ITranslator::class);
+}

+ 109 - 0
vendor/nette/utils/src/exceptions.php

@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * This file is part of the Nette Framework (https://nette.org)
+ * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
+ */
+
+declare(strict_types=1);
+
+namespace Nette;
+
+
+/**
+ * The exception that is thrown when the value of an argument is
+ * outside the allowable range of values as defined by the invoked method.
+ */
+class ArgumentOutOfRangeException extends \InvalidArgumentException
+{
+}
+
+
+/**
+ * The exception that is thrown when a method call is invalid for the object's
+ * current state, method has been invoked at an illegal or inappropriate time.
+ */
+class InvalidStateException extends \RuntimeException
+{
+}
+
+
+/**
+ * The exception that is thrown when a requested method or operation is not implemented.
+ */
+class NotImplementedException extends \LogicException
+{
+}
+
+
+/**
+ * The exception that is thrown when an invoked method is not supported. For scenarios where
+ * it is sometimes possible to perform the requested operation, see InvalidStateException.
+ */
+class NotSupportedException extends \LogicException
+{
+}
+
+
+/**
+ * The exception that is thrown when a requested method or operation is deprecated.
+ */
+class DeprecatedException extends NotSupportedException
+{
+}
+
+
+/**
+ * The exception that is thrown when accessing a class member (property or method) fails.
+ */
+class MemberAccessException extends \Error
+{
+}
+
+
+/**
+ * The exception that is thrown when an I/O error occurs.
+ */
+class IOException extends \RuntimeException
+{
+}
+
+
+/**
+ * The exception that is thrown when accessing a file that does not exist on disk.
+ */
+class FileNotFoundException extends IOException
+{
+}
+
+
+/**
+ * The exception that is thrown when part of a file or directory cannot be found.
+ */
+class DirectoryNotFoundException extends IOException
+{
+}
+
+
+/**
+ * The exception that is thrown when an argument does not match with the expected value.
+ */
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}
+
+
+/**
+ * The exception that is thrown when an illegal index was requested.
+ */
+class OutOfRangeException extends \OutOfRangeException
+{
+}
+
+
+/**
+ * The exception that is thrown when a value (typically returned by function) does not match with the expected value.
+ */
+class UnexpectedValueException extends \UnexpectedValueException
+{
+}

+ 4 - 0
vendor/open-smf/connection-pool/.gitignore

@@ -0,0 +1,4 @@
+vendor
+.idea
+composer.lock
+*.sw[a-z]

+ 21 - 0
vendor/open-smf/connection-pool/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Swoole Micro-Framework
+
+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.

+ 223 - 0
vendor/open-smf/connection-pool/README.md

@@ -0,0 +1,223 @@
+# Connection pool
+A common connection pool based on Swoole is usually used as the database connection pool.
+
+[![Latest Version](https://img.shields.io/github/release/open-smf/connection-pool.svg)](https://github.com/open-smf/connection-pool/releases)
+[![PHP Version](https://img.shields.io/packagist/php-v/open-smf/connection-pool.svg?color=green)](https://secure.php.net)
+[![Total Downloads](https://poser.pugx.org/open-smf/connection-pool/downloads)](https://packagist.org/packages/open-smf/connection-pool)
+[![License](https://poser.pugx.org/open-smf/connection-pool/license)](LICENSE)
+
+## Requirements
+
+| Dependency | Requirement |
+| -------- | -------- |
+| [PHP](https://secure.php.net/manual/en/install.php) | `>=7.0.0` |
+| [Swoole](https://github.com/swoole/swoole-src) | `>=4.2.9` `Recommend 4.2.13+` |
+
+## Install
+> Install package via [Composer](https://getcomposer.org/).
+
+```shell
+composer require "open-smf/connection-pool:~1.0"
+```
+
+## Usage
+> See more [examples](examples).
+
+- Available connectors
+
+| Connector | Connection description |
+| -------- | -------- |
+| CoroutineMySQLConnector | Instance of `Swoole\Coroutine\MySQL` |
+| CoroutinePostgreSQLConnector | Instance of `Swoole\Coroutine\PostgreSQL`, require configuring `Swoole` with `--enable-coroutine-postgresql`|
+| CoroutineRedisConnector | Instance of `Swoole\Coroutine\Redis` |
+| PhpRedisConnector | Instance of `Redis`, require [redis](https://pecl.php.net/package/redis) |
+| PDOConnector | Instance of `PDO`, require [PDO](https://www.php.net/manual/en/book.pdo.php) |
+| YourConnector | `YourConnector` must implement interface `ConnectorInterface`, any object can be used as a connection instance |
+
+- Basic usage
+
+```php
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\Connectors\CoroutineMySQLConnector;
+use Swoole\Coroutine\MySQL;
+
+go(function () {
+    // All MySQL connections: [10, 30]
+    $pool = new ConnectionPool(
+        [
+            'minActive'         => 10,
+            'maxActive'         => 30,
+            'maxWaitTime'       => 5,
+            'maxIdleTime'       => 20,
+            'idleCheckInterval' => 10,
+        ],
+        new CoroutineMySQLConnector,
+        [
+            'host'        => '127.0.0.1',
+            'port'        => '3306',
+            'user'        => 'root',
+            'password'    => 'xy123456',
+            'database'    => 'mysql',
+            'timeout'     => 10,
+            'charset'     => 'utf8mb4',
+            'strict_type' => true,
+            'fetch_mode'  => true,
+        ]
+    );
+    echo "Initializing connection pool\n";
+    $pool->init();
+    defer(function () use ($pool) {
+        echo "Closing connection pool\n";
+        $pool->close();
+    });
+
+    echo "Borrowing the connection from pool\n";
+    /**@var MySQL $connection */
+    $connection = $pool->borrow();
+
+    $status = $connection->query('SHOW STATUS LIKE "Threads_connected"');
+
+    echo "Return the connection to pool as soon as possible\n";
+    $pool->return($connection);
+
+    var_dump($status);
+});
+```
+
+- Usage in Swoole Server
+
+```php
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\ConnectionPoolTrait;
+use Smf\ConnectionPool\Connectors\CoroutineMySQLConnector;
+use Smf\ConnectionPool\Connectors\PhpRedisConnector;
+use Swoole\Coroutine\MySQL;
+use Swoole\Http\Request;
+use Swoole\Http\Response;
+use Swoole\Http\Server;
+
+class HttpServer
+{
+    use ConnectionPoolTrait;
+
+    protected $swoole;
+
+    public function __construct(string $host, int $port)
+    {
+        $this->swoole = new Server($host, $port);
+
+        $this->setDefault();
+        $this->bindWorkerEvents();
+        $this->bindHttpEvent();
+    }
+
+    protected function setDefault()
+    {
+        $this->swoole->set([
+            'daemonize'             => false,
+            'dispatch_mode'         => 1,
+            'max_request'           => 8000,
+            'open_tcp_nodelay'      => true,
+            'reload_async'          => true,
+            'max_wait_time'         => 60,
+            'enable_reuse_port'     => true,
+            'enable_coroutine'      => true,
+            'http_compression'      => false,
+            'enable_static_handler' => false,
+            'buffer_output_size'    => 4 * 1024 * 1024,
+            'worker_num'            => 4, // Each worker holds a connection pool
+        ]);
+    }
+
+    protected function bindHttpEvent()
+    {
+        $this->swoole->on('Request', function (Request $request, Response $response) {
+            $pool1 = $this->getConnectionPool('mysql');
+            /**@var MySQL $mysql */
+            $mysql = $pool1->borrow();
+            $status = $mysql->query('SHOW STATUS LIKE "Threads_connected"');
+            // Return the connection to pool as soon as possible
+            $pool1->return($mysql);
+
+
+            $pool2 = $this->getConnectionPool('redis');
+            /**@var \Redis $redis */
+            $redis = $pool2->borrow();
+            $clients = $redis->info('Clients');
+            // Return the connection to pool as soon as possible
+            $pool2->return($redis);
+
+            $json = [
+                'status'  => $status,
+                'clients' => $clients,
+            ];
+            // Other logic
+            // ...
+            $response->header('Content-Type', 'application/json');
+            $response->end(json_encode($json));
+        });
+    }
+
+    protected function bindWorkerEvents()
+    {
+        $createPools = function () {
+            // All MySQL connections: [4 workers * 2 = 8, 4 workers * 10 = 40]
+            $pool1 = new ConnectionPool(
+                [
+                    'minActive' => 2,
+                    'maxActive' => 10,
+                ],
+                new CoroutineMySQLConnector,
+                [
+                    'host'        => '127.0.0.1',
+                    'port'        => '3306',
+                    'user'        => 'root',
+                    'password'    => 'xy123456',
+                    'database'    => 'mysql',
+                    'timeout'     => 10,
+                    'charset'     => 'utf8mb4',
+                    'strict_type' => true,
+                    'fetch_mode'  => true,
+                ]);
+            $pool1->init();
+            $this->addConnectionPool('mysql', $pool1);
+
+            // All Redis connections: [4 workers * 5 = 20, 4 workers * 20 = 80]
+            $pool2 = new ConnectionPool(
+                [
+                    'minActive' => 5,
+                    'maxActive' => 20,
+                ],
+                new PhpRedisConnector,
+                [
+                    'host'     => '127.0.0.1',
+                    'port'     => '6379',
+                    'database' => 0,
+                    'password' => null,
+                ]);
+            $pool2->init();
+            $this->addConnectionPool('redis', $pool2);
+        };
+        $closePools = function () {
+            $this->closeConnectionPools();
+        };
+        $this->swoole->on('WorkerStart', $createPools);
+        $this->swoole->on('WorkerStop', $closePools);
+        $this->swoole->on('WorkerError', $closePools);
+    }
+
+    public function start()
+    {
+        $this->swoole->start();
+    }
+}
+
+// Enable coroutine for PhpRedis
+Swoole\Runtime::enableCoroutine();
+$server = new HttpServer('0.0.0.0', 5200);
+$server->start();
+```
+
+## License
+
+[MIT](LICENSE)

+ 40 - 0
vendor/open-smf/connection-pool/composer.json

@@ -0,0 +1,40 @@
+{
+  "name": "open-smf/connection-pool",
+  "type": "library",
+  "license": "MIT",
+  "support": {
+    "issues": "https://github.com/open-smf/connection-pool/issues",
+    "source": "https://github.com/open-smf/connection-pool"
+  },
+  "description": "A common connection pool based on Swoole is usually used as the database connection pool.",
+  "keywords": [
+    "swoole",
+    "connection-pool",
+    "database-connection-pool"
+  ],
+  "homepage": "https://github.com/open-smf/connection-pool",
+  "authors": [
+    {
+      "name": "Xie Biao",
+      "email": "hhxsv5@sina.com"
+    }
+  ],
+  "require": {
+    "php": ">=7.0.0",
+    "ext-json": "*",
+    "ext-swoole": ">=4.2.9"
+  },
+  "suggest": {
+    "ext-redis": "A PHP extension for Redis."
+  },
+  "autoload": {
+    "psr-4": {
+      "Smf\\ConnectionPool\\": "src"
+    }
+  },
+  "prefer-stable": true,
+  "minimum-stability": "dev",
+  "require-dev": {
+    "swoole/ide-helper": "@dev"
+  }
+}

+ 48 - 0
vendor/open-smf/connection-pool/examples/coroutine-mysql.php

@@ -0,0 +1,48 @@
+<?php
+include '../vendor/autoload.php';
+
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\Connectors\CoroutineMySQLConnector;
+use Swoole\Coroutine\MySQL;
+
+go(function () {
+    // All MySQL connections: [10, 30]
+    $pool = new ConnectionPool(
+        [
+            'minActive'         => 10,
+            'maxActive'         => 30,
+            'maxWaitTime'       => 5,
+            'maxIdleTime'       => 20,
+            'idleCheckInterval' => 10,
+        ],
+        new CoroutineMySQLConnector,
+        [
+            'host'        => '127.0.0.1',
+            'port'        => '3306',
+            'user'        => 'root',
+            'password'    => 'xy123456',
+            'database'    => 'mysql',
+            'timeout'     => 10,
+            'charset'     => 'utf8mb4',
+            'strict_type' => true,
+            'fetch_mode'  => true,
+        ]
+    );
+    echo "Initializing connection pool\n";
+    $pool->init();
+    defer(function () use ($pool) {
+        echo "Closing connection pool\n";
+        $pool->close();
+    });
+
+    echo "Borrowing the connection from pool\n";
+    /**@var MySQL $connection */
+    $connection = $pool->borrow();
+
+    $status = $connection->query('SHOW STATUS LIKE "Threads_connected"');
+
+    echo "Return the connection to pool as soon as possible\n";
+    $pool->return($connection);
+
+    var_dump($status);
+});

+ 41 - 0
vendor/open-smf/connection-pool/examples/coroutine-postgresql.php

@@ -0,0 +1,41 @@
+<?php
+include '../vendor/autoload.php';
+
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\Connectors\CoroutinePostgreSQLConnector;
+use Swoole\Coroutine\PostgreSQL;
+
+go(function () {
+    // All PostgreSQL connections: [10, 30]
+    $pool = new ConnectionPool(
+        [
+            'minActive'         => 10,
+            'maxActive'         => 30,
+            'maxWaitTime'       => 5,
+            'maxIdleTime'       => 20,
+            'idleCheckInterval' => 10,
+        ],
+        new CoroutinePostgreSQLConnector,
+        [
+            'connection_strings' => 'host=127.0.0.1 port=5432 dbname=postgres user=postgres password=xy123456',
+        ]
+    );
+    echo "Initializing connection pool\n";
+    $pool->init();
+    defer(function () use ($pool) {
+        echo "Closing connection pool\n";
+        $pool->close();
+    });
+
+    echo "Borrowing the connection from pool\n";
+    /**@var PostgreSQL $connection */
+    $connection = $pool->borrow();
+
+    $result = $connection->query("SELECT * FROM pg_stat_database where datname='postgres';");
+
+    $stat = $connection->fetchAssoc($result);
+    echo "Return the connection to pool as soon as possible\n";
+    $pool->return($connection);
+
+    var_dump($stat);
+});

+ 48 - 0
vendor/open-smf/connection-pool/examples/coroutine-redis.php

@@ -0,0 +1,48 @@
+<?php
+include '../vendor/autoload.php';
+
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\Connectors\CoroutineRedisConnector;
+use Swoole\Coroutine\Redis;
+
+go(function () {
+    // All Redis connections: [10, 30]
+    $pool = new ConnectionPool(
+        [
+            'minActive'         => 10,
+            'maxActive'         => 30,
+            'maxWaitTime'       => 5,
+            'maxIdleTime'       => 20,
+            'idleCheckInterval' => 10,
+        ],
+        new CoroutineRedisConnector,
+        [
+            'host'     => '127.0.0.1',
+            'port'     => '6379',
+            'database' => 0,
+            'password' => null,
+            'options'  => [
+                'connect_timeout' => 1,
+                'timeout'         => 5,
+            ],
+        ]
+    );
+    echo "Initializing connection pool\n";
+    $pool->init();
+    defer(function () use ($pool) {
+        echo "Close connection pool\n";
+        $pool->close();
+    });
+
+    echo "Borrowing the connection from pool\n";
+    /**@var Redis $connection */
+    $connection = $pool->borrow();
+
+    $connection->set('test', uniqid());
+    $test = $connection->get('test');
+
+    echo "Return the connection to pool as soon as possible\n";
+    $pool->return($connection);
+
+    var_dump($test);
+});

+ 49 - 0
vendor/open-smf/connection-pool/examples/coroutine-runtime-pdo.php

@@ -0,0 +1,49 @@
+<?php
+include '../vendor/autoload.php';
+
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\Connectors\PDOConnector;
+
+// Enable coroutine for PDO
+Swoole\Runtime::enableCoroutine();
+
+go(function () {
+    // All PDO connections: [10, 30]
+    $pool = new ConnectionPool(
+        [
+            'minActive'         => 10,
+            'maxActive'         => 30,
+            'maxWaitTime'       => 5,
+            'maxIdleTime'       => 20,
+            'idleCheckInterval' => 10,
+        ],
+        new PDOConnector,
+        [
+            'dsn'      => 'mysql:host=127.0.0.1;port=3306;dbname=mysql;charset=utf8mb4',
+            'username' => 'root',
+            'password' => 'xy123456',
+            'options'  => [
+                \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
+                \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
+                \PDO::ATTR_TIMEOUT            => 30,
+            ],
+        ]
+    );
+    echo "Initializing connection pool\n";
+    $pool->init();
+    defer(function () use ($pool) {
+        echo "Close connection pool\n";
+        $pool->close();
+    });
+
+    echo "Borrowing the connection from pool\n";
+    /**@var \PDO $connection */
+    $connection = $pool->borrow();
+
+    $statement = $connection->query('SHOW STATUS LIKE "Threads_connected"');
+
+    echo "Return the connection to pool as soon as possible\n";
+    $pool->return($connection);
+
+    var_dump($statement->fetch());
+});

+ 47 - 0
vendor/open-smf/connection-pool/examples/coroutine-runtime-phpredis.php

@@ -0,0 +1,47 @@
+<?php
+include '../vendor/autoload.php';
+
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\Connectors\PhpRedisConnector;
+
+// Enable coroutine for PhpRedis
+Swoole\Runtime::enableCoroutine();
+
+go(function () {
+    // All Redis connections: [10, 30]
+    $pool = new ConnectionPool(
+        [
+            'minActive'         => 10,
+            'maxActive'         => 30,
+            'maxWaitTime'       => 5,
+            'maxIdleTime'       => 20,
+            'idleCheckInterval' => 10,
+        ],
+        new PhpRedisConnector,
+        [
+            'host'     => '127.0.0.1',
+            'port'     => '6379',
+            'database' => 0,
+            'password' => null,
+            'timeout'  => 5,
+        ]
+    );
+    echo "Initializing connection pool\n";
+    $pool->init();
+    defer(function () use ($pool) {
+        echo "Close connection pool\n";
+        $pool->close();
+    });
+
+    echo "Borrowing the connection from pool\n";
+    /**@var Redis $connection */
+    $connection = $pool->borrow();
+
+    $connection->set('test', uniqid());
+    $test = $connection->get('test');
+
+    echo "Return the connection to pool as soon as possible\n";
+    $pool->return($connection);
+
+    var_dump($test);
+});

+ 65 - 0
vendor/open-smf/connection-pool/examples/dynamic-testing.php

@@ -0,0 +1,65 @@
+<?php
+include '../vendor/autoload.php';
+
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\Connectors\PDOConnector;
+use Swoole\Coroutine;
+
+Swoole\Runtime::enableCoroutine();
+
+go(function () {
+    // All MySQL connections: [10, 30]
+    $pool = new ConnectionPool(
+        [
+            'minActive'         => 10,
+            'maxActive'         => 30,
+            'maxWaitTime'       => 5,
+            'maxIdleTime'       => 20,
+            'idleCheckInterval' => 10,
+        ],
+        new PDOConnector,
+        [
+            'dsn'      => 'mysql:host=127.0.0.1;port=3306;dbname=mysql;charset=utf8mb4',
+            'username' => 'root',
+            'password' => 'xy123456',
+            'options'  => [
+                \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
+                \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
+                \PDO::ATTR_TIMEOUT            => 30,
+            ],
+        ]
+    );
+    $pool->init();
+
+    // For debug
+    $peakCount = 0;
+    swoole_timer_tick(1000, function () use ($pool, &$peakCount) {
+        $count = $pool->getConnectionCount();
+        $idleCount = $pool->getIdleCount();
+        if ($peakCount < $count) {
+            $peakCount = $count;
+        }
+        echo "Pool connection count: $count, peak count: $peakCount, idle count: $idleCount\n";
+    });
+
+    while (true) {
+        $count = mt_rand(1, 45);
+        echo "Query count: $count\n";
+        for ($i = 0; $i < $count; $i++) {
+            go(function () use ($pool) {
+                /**@var \PDO $pdo */
+                $pdo = $pool->borrow();
+                defer(function () use ($pool, $pdo) {
+                    $pool->return($pdo);
+                });
+                $statement = $pdo->query('show status like \'Threads_connected\'');
+                $ret = $statement->fetch();
+                if (!isset($ret['Variable_name'])) {
+                    echo "Invalid query result: \n", print_r($ret, true);
+                }
+                echo $ret['Variable_name'] . ': ' . $ret['Value'] . "\n";
+            });
+        }
+        Coroutine::sleep(mt_rand(1, 15));
+    }
+});

+ 132 - 0
vendor/open-smf/connection-pool/examples/http-server.php

@@ -0,0 +1,132 @@
+<?php
+include '../vendor/autoload.php';
+
+use Smf\ConnectionPool\ConnectionPool;
+use Smf\ConnectionPool\ConnectionPoolTrait;
+use Smf\ConnectionPool\Connectors\CoroutineMySQLConnector;
+use Smf\ConnectionPool\Connectors\PhpRedisConnector;
+use Swoole\Coroutine\MySQL;
+use Swoole\Http\Request;
+use Swoole\Http\Response;
+use Swoole\Http\Server;
+
+class HttpServer
+{
+    use ConnectionPoolTrait;
+
+    protected $swoole;
+
+    public function __construct(string $host, int $port)
+    {
+        $this->swoole = new Server($host, $port);
+
+        $this->setDefault();
+        $this->bindWorkerEvents();
+        $this->bindHttpEvent();
+    }
+
+    protected function setDefault()
+    {
+        $this->swoole->set([
+            'daemonize'             => false,
+            'dispatch_mode'         => 1,
+            'max_request'           => 8000,
+            'open_tcp_nodelay'      => true,
+            'reload_async'          => true,
+            'max_wait_time'         => 60,
+            'enable_reuse_port'     => true,
+            'enable_coroutine'      => true,
+            'http_compression'      => false,
+            'enable_static_handler' => false,
+            'buffer_output_size'    => 4 * 1024 * 1024,
+            'worker_num'            => 4, // Each worker holds a connection pool
+        ]);
+    }
+
+    protected function bindHttpEvent()
+    {
+        $this->swoole->on('Request', function (Request $request, Response $response) {
+            $pool1 = $this->getConnectionPool('mysql');
+            /**@var MySQL $mysql */
+            $mysql = $pool1->borrow();
+            $status = $mysql->query('SHOW STATUS LIKE "Threads_connected"');
+            // Return the connection to pool as soon as possible
+            $pool1->return($mysql);
+
+
+            $pool2 = $this->getConnectionPool('redis');
+            /**@var \Redis $redis */
+            $redis = $pool2->borrow();
+            $clients = $redis->info('Clients');
+            // Return the connection to pool as soon as possible
+            $pool2->return($redis);
+
+            $json = [
+                'status'  => $status,
+                'clients' => $clients,
+            ];
+            // Other logic
+            // ...
+            $response->header('Content-Type', 'application/json');
+            $response->end(json_encode($json));
+        });
+    }
+
+    protected function bindWorkerEvents()
+    {
+        $createPools = function () {
+            // All MySQL connections: [4 workers * 2 = 8, 4 workers * 10 = 40]
+            $pool1 = new ConnectionPool(
+                [
+                    'minActive' => 2,
+                    'maxActive' => 10,
+                ],
+                new CoroutineMySQLConnector,
+                [
+                    'host'        => '127.0.0.1',
+                    'port'        => '3306',
+                    'user'        => 'root',
+                    'password'    => 'xy123456',
+                    'database'    => 'mysql',
+                    'timeout'     => 10,
+                    'charset'     => 'utf8mb4',
+                    'strict_type' => true,
+                    'fetch_mode'  => true,
+                ]);
+            $pool1->init();
+            $this->addConnectionPool('mysql', $pool1);
+
+            // All Redis connections: [4 workers * 5 = 20, 4 workers * 20 = 80]
+            $pool2 = new ConnectionPool(
+                [
+                    'minActive' => 5,
+                    'maxActive' => 20,
+                ],
+                new PhpRedisConnector,
+                [
+                    'host'     => '127.0.0.1',
+                    'port'     => '6379',
+                    'database' => 0,
+                    'password' => null,
+                ]);
+            $pool2->init();
+            $this->addConnectionPool('redis', $pool2);
+        };
+        $closePools = function () {
+            $this->closeConnectionPools();
+        };
+        $this->swoole->on('WorkerStart', $createPools);
+        $this->swoole->on('WorkerStop', $closePools);
+        $this->swoole->on('WorkerError', $closePools);
+    }
+
+    public function start()
+    {
+        $this->swoole->start();
+    }
+}
+
+// Enable coroutine for PhpRedis
+Swoole\Runtime::enableCoroutine();
+$server = new HttpServer('0.0.0.0', 5200);
+$server->start();

+ 19 - 0
vendor/open-smf/connection-pool/src/BorrowConnectionTimeoutException.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace Smf\ConnectionPool;
+
+class BorrowConnectionTimeoutException extends \Exception
+{
+    protected $timeout;
+
+    public function getTimeout(): float
+    {
+        return $this->timeout;
+    }
+
+    public function setTimeout(float $timeout): self
+    {
+        $this->timeout = $timeout;
+        return $this;
+    }
+}

+ 279 - 0
vendor/open-smf/connection-pool/src/ConnectionPool.php

@@ -0,0 +1,279 @@
+<?php
+
+namespace Smf\ConnectionPool;
+
+use Smf\ConnectionPool\Connectors\ConnectorInterface;
+use Swoole\Coroutine\Channel;
+use Swoole\Coroutine;
+
+class ConnectionPool implements ConnectionPoolInterface
+{
+    /**@var float The timeout of the operation channel */
+    const CHANNEL_TIMEOUT = 0.001;
+
+    /**@var float The minimum interval to check the idle connections */
+    const MIN_CHECK_IDLE_INTERVAL = 10;
+
+    /**@var string The key about the last active time of connection */
+    const KEY_LAST_ACTIVE_TIME = '__lat';
+
+    /**@var bool Whether the connection pool is initialized */
+    protected $initialized;
+
+    /**@var bool Whether the connection pool is closed */
+    protected $closed;
+
+    /**@var Channel The connection pool */
+    protected $pool;
+
+    /**@var ConnectorInterface The connector */
+    protected $connector;
+
+    /**@var array The config of connection */
+    protected $connectionConfig;
+
+    /**@var int Current all connection count */
+    protected $connectionCount = 0;
+
+    /**@var int The minimum number of active connections */
+    protected $minActive = 1;
+
+    /**@var int The maximum number of active connections */
+    protected $maxActive = 1;
+
+    /**@var float The maximum waiting time for connection, when reached, an exception will be thrown */
+    protected $maxWaitTime = 5;
+
+    /**@var float The maximum idle time for the connection, when reached, the connection will be removed from pool, and keep the least $minActive connections in the pool */
+    protected $maxIdleTime = 5;
+
+    /**@var float The interval to check idle connection */
+    protected $idleCheckInterval = 5;
+
+    /**@var int The timer id of balancer */
+    protected $balancerTimerId;
+
+    /**
+     * ConnectionPool constructor.
+     * @param array $poolConfig The minimum number of active connections, the detail keys:
+     * int minActive The minimum number of active connections
+     * int maxActive The maximum number of active connections
+     * float maxWaitTime The maximum waiting time for connection, when reached, an exception will be thrown
+     * float maxIdleTime The maximum idle time for the connection, when reached, the connection will be removed from pool, and keep the least $minActive connections in the pool
+     * float idleCheckInterval The interval to check idle connection
+     * @param ConnectorInterface $connector The connector instance of ConnectorInterface
+     * @param array $connectionConfig The config of connection
+     */
+    public function __construct(array $poolConfig, ConnectorInterface $connector, array $connectionConfig)
+    {
+        $this->initialized = false;
+        $this->closed = false;
+        $this->minActive = $poolConfig['minActive'] ?? 20;
+        $this->maxActive = $poolConfig['maxActive'] ?? 100;
+        $this->maxWaitTime = $poolConfig['maxWaitTime'] ?? 5;
+        $this->maxIdleTime = $poolConfig['maxIdleTime'] ?? 30;
+        $poolConfig['idleCheckInterval'] = $poolConfig['idleCheckInterval'] ?? 15;
+        $this->idleCheckInterval = $poolConfig['idleCheckInterval'] >= static::MIN_CHECK_IDLE_INTERVAL ? $poolConfig['idleCheckInterval'] : static::MIN_CHECK_IDLE_INTERVAL;
+        $this->connectionConfig = $connectionConfig;
+        $this->connector = $connector;
+    }
+
+    /**
+     * Initialize the connection pool
+     * @return bool
+     */
+    public function init(): bool
+    {
+        if ($this->initialized) {
+            return false;
+        }
+        $this->initialized = true;
+        $this->pool = new Channel($this->maxActive);
+        $this->balancerTimerId = $this->startBalanceTimer($this->idleCheckInterval);
+        Coroutine::create(function () {
+            for ($i = 0; $i < $this->minActive; $i++) {
+                $connection = $this->createConnection();
+                $ret = $this->pool->push($connection, static::CHANNEL_TIMEOUT);
+                if ($ret === false) {
+                    $this->removeConnection($connection);
+                }
+            }
+        });
+        return true;
+    }
+
+    /**
+     * Borrow a connection from the connection pool, throw an exception if timeout
+     * @return mixed The connection resource
+     * @throws BorrowConnectionTimeoutException
+     * @throws \RuntimeException
+     */
+    public function borrow()
+    {
+        if (!$this->initialized) {
+            throw new \RuntimeException('Please initialize the connection pool first, call $pool->init().');
+        }
+        if ($this->pool->isEmpty()) {
+            // Create more connections
+            if ($this->connectionCount < $this->maxActive) {
+                return $this->createConnection();
+            }
+        }
+
+        $connection = $this->pool->pop($this->maxWaitTime);
+        if ($connection === false) {
+            $exception = new BorrowConnectionTimeoutException(sprintf(
+                'Borrow the connection timeout in %.2f(s), connections in pool: %d, all connections: %d',
+                $this->maxWaitTime,
+                $this->pool->length(),
+                $this->connectionCount
+            ));
+            $exception->setTimeout($this->maxWaitTime);
+            throw $exception;
+        }
+        if ($this->connector->isConnected($connection)) {
+            // Reset the connection for the connected connection
+            $this->connector->reset($connection, $this->connectionConfig);
+        } else {
+            // Remove the disconnected connection, then create a new connection
+            $this->removeConnection($connection);
+            $connection = $this->createConnection();
+        }
+        return $connection;
+    }
+
+    /**
+     * Return a connection to the connection pool
+     * @param mixed $connection The connection resource
+     * @return bool
+     */
+    public function return($connection): bool
+    {
+        if (!$this->connector->validate($connection)) {
+            throw new \RuntimeException('Connection of unexpected type returned.');
+        }
+
+        if (!$this->initialized) {
+            throw new \RuntimeException('Please initialize the connection pool first, call $pool->init().');
+        }
+        if ($this->pool->isFull()) {
+            // Discard the connection
+            $this->removeConnection($connection);
+            return false;
+        }
+        $connection->{static::KEY_LAST_ACTIVE_TIME} = time();
+        $ret = $this->pool->push($connection, static::CHANNEL_TIMEOUT);
+        if ($ret === false) {
+            $this->removeConnection($connection);
+        }
+        return true;
+    }
+
+    /**
+     * Get the number of created connections
+     * @return int
+     */
+    public function getConnectionCount(): int
+    {
+        return $this->connectionCount;
+    }
+
+    /**
+     * Get the number of idle connections
+     * @return int
+     */
+    public function getIdleCount(): int
+    {
+        return $this->pool->length();
+    }
+
+    /**
+     * Close the connection pool and disconnect all connections
+     * @return bool
+     */
+    public function close(): bool
+    {
+        if (!$this->initialized) {
+            return false;
+        }
+        if ($this->closed) {
+            return false;
+        }
+        $this->closed = true;
+        swoole_timer_clear($this->balancerTimerId);
+        Coroutine::create(function () {
+            while (true) {
+                if ($this->pool->isEmpty()) {
+                    break;
+                }
+                $connection = $this->pool->pop(static::CHANNEL_TIMEOUT);
+                if ($connection !== false) {
+                    $this->connector->disconnect($connection);
+                }
+            }
+            $this->pool->close();
+        });
+        return true;
+    }
+
+    public function __destruct()
+    {
+        $this->close();
+    }
+
+    protected function startBalanceTimer(float $interval)
+    {
+        return swoole_timer_tick(round($interval) * 1000, function () {
+            $now = time();
+            $validConnections = [];
+            while (true) {
+                if ($this->closed) {
+                    break;
+                }
+                if ($this->connectionCount <= $this->minActive) {
+                    break;
+                }
+                if ($this->pool->isEmpty()) {
+                    break;
+                }
+                $connection = $this->pool->pop(static::CHANNEL_TIMEOUT);
+                if ($connection === false) {
+                    continue;
+                }
+                $lastActiveTime = $connection->{static::KEY_LAST_ACTIVE_TIME} ?? 0;
+                if ($now - $lastActiveTime < $this->maxIdleTime) {
+                    $validConnections[] = $connection;
+                } else {
+                    $this->removeConnection($connection);
+                }
+            }
+
+            foreach ($validConnections as $validConnection) {
+                $ret = $this->pool->push($validConnection, static::CHANNEL_TIMEOUT);
+                if ($ret === false) {
+                    $this->removeConnection($validConnection);
+                }
+            }
+        });
+    }
+
+    protected function createConnection()
+    {
+        $this->connectionCount++;
+        $connection = $this->connector->connect($this->connectionConfig);
+        $connection->{static::KEY_LAST_ACTIVE_TIME} = time();
+        return $connection;
+    }
+
+    protected function removeConnection($connection)
+    {
+        $this->connectionCount--;
+        Coroutine::create(function () use ($connection) {
+            try {
+                $this->connector->disconnect($connection);
+            } catch (\Throwable $e) {
+                // Ignore this exception.
+            }
+        });
+    }
+}

+ 33 - 0
vendor/open-smf/connection-pool/src/ConnectionPoolInterface.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Smf\ConnectionPool;
+
+interface ConnectionPoolInterface
+{
+
+    /**
+     * Initialize the connection pool
+     * @return bool
+     */
+    public function init(): bool;
+
+    /**
+     * Return a connection to the connection pool
+     * @param mixed $connection
+     * @return bool
+     */
+    public function return($connection): bool;
+
+    /**
+     * Borrow a connection to the connection pool
+     * @return mixed
+     * @throws BorrowConnectionTimeoutException
+     */
+    public function borrow();
+
+    /**
+     * Close the connection pool, release the resource of all connections
+     * @return bool
+     */
+    public function close(): bool;
+}

+ 51 - 0
vendor/open-smf/connection-pool/src/ConnectionPoolTrait.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace Smf\ConnectionPool;
+
+trait ConnectionPoolTrait
+{
+    /**
+     * @var ConnectionPool[] $pools
+     */
+    protected $pools = [];
+
+    /**
+     * Add a connection pool
+     * @param string $key
+     * @param ConnectionPool $pool
+     */
+    public function addConnectionPool(string $key, ConnectionPool $pool)
+    {
+        $this->pools[$key] = $pool;
+    }
+
+    /**
+     * Get a connection pool by key
+     * @param string $key
+     * @return ConnectionPool
+     */
+    public function getConnectionPool(string $key): ConnectionPool
+    {
+        return $this->pools[$key];
+    }
+
+    /**
+     * Close the connection by key
+     * @param string $key
+     * @return bool
+     */
+    public function closeConnectionPool(string $key)
+    {
+        return $this->pools[$key]->close();
+    }
+
+    /**
+     * Close all connection pools
+     */
+    public function closeConnectionPools()
+    {
+        foreach ($this->pools as $pool) {
+            $pool->close();
+        }
+    }
+}

+ 43 - 0
vendor/open-smf/connection-pool/src/Connectors/ConnectorInterface.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Smf\ConnectionPool\Connectors;
+
+interface ConnectorInterface
+{
+    /**
+     * Connect to the specified Server and returns the connection resource
+     * @param array $config
+     * @return mixed
+     */
+    public function connect(array $config);
+
+    /**
+     * Disconnect and free resources
+     * @param mixed $connection
+     * @return mixed
+     */
+    public function disconnect($connection);
+
+    /**
+     * Whether the connection is established
+     * @param mixed $connection
+     * @return bool
+     */
+    public function isConnected($connection): bool;
+
+    /**
+     * Reset the connection
+     * @param mixed $connection
+     * @param array $config
+     * @return mixed
+     */
+    public function reset($connection, array $config);
+
+    /**
+     * Validate the connection
+     *
+     * @param mixed $connection
+     * @return bool
+     */
+    public function validate($connection): bool;
+}

+ 39 - 0
vendor/open-smf/connection-pool/src/Connectors/CoroutineMySQLConnector.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace Smf\ConnectionPool\Connectors;
+
+use Swoole\Coroutine\MySQL;
+
+class CoroutineMySQLConnector implements ConnectorInterface
+{
+    public function connect(array $config)
+    {
+        $connection = new MySQL();
+        if ($connection->connect($config) === false) {
+            throw new \RuntimeException(sprintf('Failed to connect MySQL server: [%d] %s', $connection->connect_errno, $connection->connect_error));
+        }
+        return $connection;
+    }
+
+    public function disconnect($connection)
+    {
+        /**@var MySQL $connection */
+        $connection->close();
+    }
+
+    public function isConnected($connection): bool
+    {
+        /**@var MySQL $connection */
+        return $connection->connected;
+    }
+
+    public function reset($connection, array $config)
+    {
+
+    }
+
+    public function validate($connection): bool
+    {
+        return $connection instanceof MySQL;
+    }
+}

+ 42 - 0
vendor/open-smf/connection-pool/src/Connectors/CoroutinePostgreSQLConnector.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace Smf\ConnectionPool\Connectors;
+
+use Swoole\Coroutine\PostgreSQL;
+
+class CoroutinePostgreSQLConnector implements ConnectorInterface
+{
+    public function connect(array $config)
+    {
+        if (!isset($config['connection_strings'])) {
+            throw new \InvalidArgumentException('The key "connection_string" is missing.');
+        }
+        $connection = new PostgreSQL();
+        $ret = $connection->connect($config['connection_strings']);
+        if ($ret === false) {
+            throw new \RuntimeException(sprintf('Failed to connect PostgreSQL server: %s', $connection->error));
+        }
+        return $connection;
+    }
+
+    public function disconnect($connection)
+    {
+        /**@var PostgreSQL $connection */
+    }
+
+    public function isConnected($connection): bool
+    {
+        /**@var PostgreSQL $connection */
+        return true;
+    }
+
+    public function reset($connection, array $config)
+    {
+        /**@var PostgreSQL $connection */
+    }
+
+    public function validate($connection): bool
+    {
+        return $connection instanceof PostgreSQL;
+    }
+}

+ 53 - 0
vendor/open-smf/connection-pool/src/Connectors/CoroutineRedisConnector.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace Smf\ConnectionPool\Connectors;
+
+use Swoole\Coroutine\Redis;
+
+class CoroutineRedisConnector implements ConnectorInterface
+{
+    public function connect(array $config)
+    {
+        $connection = new Redis($config['options'] ?? []);
+        $ret = $connection->connect($config['host'], $config['port']);
+        if ($ret === false) {
+            throw new \RuntimeException(sprintf('Failed to connect Redis server: [%s] %s', $connection->errCode, $connection->errMsg));
+        }
+        if (isset($config['password'])) {
+            $config['password'] = (string)$config['password'];
+            if ($config['password'] !== '') {
+                $connection->auth($config['password']);
+            }
+        }
+        if (isset($config['database'])) {
+            $connection->select($config['database']);
+        }
+        return $connection;
+    }
+
+    public function disconnect($connection)
+    {
+        /**@var Redis $connection */
+        $connection->close();
+    }
+
+    public function isConnected($connection): bool
+    {
+        /**@var Redis $connection */
+        return $connection->connected;
+    }
+
+    public function reset($connection, array $config)
+    {
+        /**@var Redis $connection */
+        $connection->setDefer(false);
+        if (isset($config['database'])) {
+            $connection->select($config['database']);
+        }
+    }
+
+    public function validate($connection): bool
+    {
+        return $connection instanceof Redis;
+    }
+}

+ 42 - 0
vendor/open-smf/connection-pool/src/Connectors/PDOConnector.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace Smf\ConnectionPool\Connectors;
+
+class PDOConnector implements ConnectorInterface
+{
+    public function connect(array $config)
+    {
+        try {
+            $connection = new \PDO($config['dsn'], $config['username'] ?? '', $config['password'] ?? '', $config['options'] ?? []);
+        } catch (\Throwable $e) {
+            throw new \RuntimeException(sprintf('Failed to connect the requested database: [%d] %s', $e->getCode(), $e->getMessage()));
+        }
+        return $connection;
+    }
+
+    public function disconnect($connection)
+    {
+        /**@var \PDO $connection */
+        $connection = null;
+    }
+
+    public function isConnected($connection): bool
+    {
+        /**@var \PDO $connection */
+        try {
+            return !!@$connection->getAttribute(\PDO::ATTR_SERVER_INFO);
+        } catch (\Throwable $e) {
+            return false;
+        }
+    }
+
+    public function reset($connection, array $config)
+    {
+
+    }
+
+    public function validate($connection): bool
+    {
+        return $connection instanceof \PDO;
+    }
+}

+ 53 - 0
vendor/open-smf/connection-pool/src/Connectors/PhpRedisConnector.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace Smf\ConnectionPool\Connectors;
+
+class PhpRedisConnector implements ConnectorInterface
+{
+    public function connect(array $config)
+    {
+        $connection = new \Redis();
+        $ret = $connection->connect($config['host'], $config['port'], $config['timeout'] ?? 10);
+        if ($ret === false) {
+            throw new \RuntimeException(sprintf('Failed to connect Redis server: %s', $connection->getLastError()));
+        }
+        if (isset($config['password'])) {
+            $config['password'] = (string)$config['password'];
+            if ($config['password'] !== '') {
+                $connection->auth($config['password']);
+            }
+        }
+        if (isset($config['database'])) {
+            $connection->select($config['database']);
+        }
+        foreach ($config['options'] ?? [] as $key => $value) {
+            $connection->setOption($key, $value);
+        }
+        return $connection;
+    }
+
+    public function disconnect($connection)
+    {
+        /**@var \Redis $connection */
+        $connection->close();
+    }
+
+    public function isConnected($connection): bool
+    {
+        /**@var \Redis $connection */
+        return $connection->isConnected();
+    }
+
+    public function reset($connection, array $config)
+    {
+        /**@var \Redis $connection */
+        if (isset($config['database'])) {
+            $connection->select($config['database']);
+        }
+    }
+
+    public function validate($connection): bool
+    {
+        return $connection instanceof \Redis;
+    }
+}

+ 3 - 2
vendor/services.php

@@ -1,7 +1,8 @@
 <?php 
-// This file is automatically generated at:2021-09-07 15:41:54
+// This file is automatically generated at:2021-09-08 14:06:07
 declare (strict_types = 1);
 return array (
   0 => 'think\\queue\\Service',
-  1 => 'think\\trace\\Service',
+  1 => 'think\\swoole\\Service',
+  2 => 'think\\trace\\Service',
 );

+ 3 - 0
vendor/stechstudio/backoff/.gitignore

@@ -0,0 +1,3 @@
+vendor
+.idea
+composer.lock

+ 21 - 0
vendor/stechstudio/backoff/LICENSE.md

@@ -0,0 +1,21 @@
+# The MIT License (MIT)
+
+Copyright (c) Signature Tech Studio, Inc <info@stechstudio.com>
+
+> 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.

+ 216 - 0
vendor/stechstudio/backoff/README.md

@@ -0,0 +1,216 @@
+# PHP Backoff
+
+[![Build](https://img.shields.io/scrutinizer/build/g/stechstudio/backoff.svg?style=flat-square)](https://scrutinizer-ci.com/g/stechstudio/backoff)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
+[![Quality Score](https://img.shields.io/scrutinizer/g/stechstudio/backoff.svg?style=flat-square)](https://scrutinizer-ci.com/g/stechstudio/backoff)
+[![Total Downloads](https://img.shields.io/packagist/dt/stechstudio/backoff.svg?style=flat-square)](https://packagist.org/packages/stechstudio/backoff)
+
+Easily wrap your code with retry functionality. This library provides:
+
+ 1. 4 backoff strategies (plus the ability to use your own)
+ 2. Optional jitter / randomness to spread out retries and minimize collisions
+ 3. Wait time cap
+ 4. Callbacks for custom retry logic or error handling
+
+## Installation
+
+```
+composer require stechstudio/backoff
+```
+
+## Defaults
+
+This library provides sane defaults so you can hopefully just jump in for most of your use cases.
+
+By default the backoff is quadratic with a 100ms base time (`attempt^2 * 100`), a max of 5 retries, and no jitter.
+
+## Quickstart
+
+The simplest way to use Backoff is with the global `backoff` helper function:
+
+```
+$result = backoff(function() {
+    return doSomeWorkThatMightFail();
+});
+```
+
+If successful `$result` will contain the result of the closure. If max attempts are exceeded the inner exception is re-thrown.
+
+You can of course provide other options via the helper method if needed.
+
+Method parameters are `$callback`, `$maxAttempts`, `$strategy`, `$waitCap`, `$useJitter`.
+
+## Backoff class usage
+
+The Backoff class constructor parameters are `$maxAttempts`, `$strategy`, `$waitCap`, `$useJitter`.
+
+```
+$backoff = new Backoff(10, 'exponential', 10000, true);
+$result = $backoff->run(function() {
+    return doSomeWorkThatMightFail();
+});
+```
+
+Or if you are injecting the Backoff class with a dependency container, you can set it up with setters after the fact. Note that setters are chainable.
+
+```
+// Assuming a fresh instance of $backoff was handed to you
+$result = $backoff
+    ->setStrategy('constant')
+    ->setMaxAttempts(10)
+    ->enableJitter()
+    ->run(function() {
+        return doSomeWorkThatMightFail();
+    });
+```
+
+## Changing defaults
+
+If you find you want different defaults, you can modify them via static class properties:
+
+```
+Backoff::$defaultMaxAttempts = 10;
+Backoff::$defaultStrategy = 'exponential';
+Backoff::$defaultJitterEnabled = true;
+```
+
+You might want to do this somewhere in your application bootstrap for example. These defaults will be used anytime you create an instance of the Backoff class or use the `backoff()` helper function.
+
+## Strategies
+
+There are four built-in strategies available: constant, linear, polynomial, and exponential.
+
+The default base time for all strategies is 100 milliseconds.
+
+### Constant
+
+```
+$strategy = new ConstantStrategy(500);
+```
+
+This strategy will sleep for 500 milliseconds on each retry loop.
+
+### Linear
+
+```
+$strategy = new LinearStrategy(200);
+```
+
+This strategy will sleep for `attempt * baseTime`, providing linear backoff starting at 200 milliseconds.
+
+### Polynomial
+
+```
+$strategy = new PolynomialStrategy(100, 3);
+```
+
+This strategy will sleep for `(attempt^degree) * baseTime`, so in this case `(attempt^3) * 100`.
+
+The default degree if none provided is 2, effectively quadratic time.
+
+### Exponential
+
+```
+$strategy = new ExponentialStrategy(100);
+```
+
+This strategy will sleep for `(2^attempt) * baseTime`.
+
+## Specifying strategy
+
+In our earlier code examples we specified the strategy as a string:
+
+```
+backoff(function() {
+    ...
+}, 10, 'constant');
+
+// OR
+
+$backoff = new Backoff(10, 'constant');
+```
+
+This would use the `ConstantStrategy` with defaults, effectively giving you a 100 millisecond sleep time.
+
+You can create the strategy instance yourself in order to modify these defaults:
+
+```
+backoff(function() {
+    ...
+}, 10, new LinearStrategy(500));
+
+// OR
+
+$backoff = new Backoff(10, new LinearStrategy(500));
+```
+
+You can also pass in an integer as the strategy, will translates to a ConstantStrategy with the integer as the base time in milliseconds:
+
+```
+backoff(function() {
+    ...
+}, 10, 1000);
+
+// OR
+
+$backoff = new Backoff(10, 1000);
+```
+
+Finally, you can pass in a closure as the strategy if you wish. This closure should receive an integer `attempt` and return a sleep time in milliseconds.
+
+```
+backoff(function() {
+    ...
+}, 10, function($attempt) {
+    return (100 * $attempt) + 5000;
+});
+
+// OR
+
+$backoff = new Backoff(10);
+$backoff->setStrategy(function($attempt) {
+    return (100 * $attempt) + 5000;
+});
+```
+
+## Wait cap
+
+You may want to use a fast growing backoff time (like exponential) but then also set a max wait time so that it levels out after a while.
+
+This cap can be provided as the fourth argument to the `backoff` helper function, or using the `setWaitCap()` method on the Backoff class.
+
+## Jitter
+
+If you have a lot of clients starting a job at the same time and encountering failures, any of the above backoff strategies could mean the workers continue to collide at each retry.
+
+The solution for this is to add randomness. See here for a good explanation:
+
+https://www.awsarchitectureblog.com/2015/03/backoff.html
+
+You can enable jitter by passing `true` in as the fifth argument to the `backoff` helper function, or by using the `enableJitter()` method on the Backoff class.
+
+We use the "FullJitter" approach outlined in the above article, where a random number between 0 and the sleep time provided by your selected strategy is used.
+
+## Custom retry decider
+
+By default Backoff will retry if an exception is encountered, and if it has not yet hit max retries.
+
+You may provide your own retry decider for more advanced use cases. Perhaps you want to retry based on time rather than number of retries, or perhaps there are scenarios where you would want retry even when an exception was not encountered.
+
+Provide the decider as a callback, or an instance of a class with an `__invoke` method. Backoff will hand it four parameters: the current attempt, max attempts, the last result received, and the exception if one was encountered. Your decider needs to return true or false.
+
+```php
+$backoff->setDecider(function($attempt, $maxAttempts, $result, $exception = null) {
+    return someCustomLogic();
+});
+```
+
+## Error handler callback
+
+You can provide a custom error handler to be notified anytime an exception occurs, even if we have yet to reach max attempts. This is a useful place to do logging for example.
+
+```php
+$backoff->setErrorHandler(function($exception, $attempt, $maxAttempts) {
+    Log::error("On run $attempt we hit a problem: " . $exception->getMessage());
+});
+```

+ 23 - 0
vendor/stechstudio/backoff/composer.json

@@ -0,0 +1,23 @@
+{
+    "name": "stechstudio/backoff",
+    "description": "PHP library providing retry functionality with multiple backoff strategies and jitter support",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Joseph Szobody",
+            "email": "joseph@stechstudio.com"
+        }
+    ],
+    "require": {},
+    "require-dev": {
+        "phpunit/phpunit": "5.5.*"
+    },
+    "autoload": {
+        "psr-4":{
+            "STS\\Backoff\\": "src"
+        },
+        "files": [
+            "src/helpers.php"
+        ]
+    }
+}

+ 17 - 0
vendor/stechstudio/backoff/phpunit.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         bootstrap="vendor/autoload.php"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="false">
+    <testsuites>
+        <testsuite name="Backoff Test Suite">
+            <directory>./tests</directory>
+        </testsuite>
+    </testsuites>
+</phpunit>

+ 348 - 0
vendor/stechstudio/backoff/src/Backoff.php

@@ -0,0 +1,348 @@
+<?php
+namespace STS\Backoff;
+
+use Exception;
+use InvalidArgumentException;
+use STS\Backoff\Strategies\ConstantStrategy;
+use STS\Backoff\Strategies\ExponentialStrategy;
+use STS\Backoff\Strategies\LinearStrategy;
+use STS\Backoff\Strategies\PolynomialStrategy;
+
+/**
+ * Class Retry
+ * @package STS\Backoff
+ */
+class Backoff
+{
+    /**
+     * @var string
+     */
+    public static $defaultStrategy = "polynomial";
+
+    /**
+     * @var int
+     */
+    public static $defaultMaxAttempts = 5;
+
+    /**
+     * @var bool
+     */
+    public static $defaultJitterEnabled = false;
+
+    /**
+     * This callable should take an 'attempt' integer, and return a wait time in milliseconds
+     *
+     * @var callable
+     */
+    protected $strategy;
+
+    /**
+     * @var array
+     */
+    protected $strategies = [
+        'constant'    => ConstantStrategy::class,
+        'linear'      => LinearStrategy::class,
+        'polynomial'  => PolynomialStrategy::class,
+        'exponential' => ExponentialStrategy::class
+    ];
+
+    /**
+     * @var int
+     */
+    protected $maxAttempts;
+
+    /**
+     * The max wait time you want to allow, regardless of what the strategy says
+     *
+     * @var int|null     In milliseconds
+     */
+    protected $waitCap;
+
+    /**
+     * @var bool
+     */
+    protected $useJitter = false;
+
+    /**
+     * @var array
+     */
+    protected $exceptions = [];
+
+    /**
+     * This will decide whether to retry or not.
+     * @var callable
+     */
+    protected $decider;
+
+    /**
+     * This receive any exceptions we encounter.
+     * @var callable
+     */
+    protected $errorHandler;
+
+    /**
+     * @param int $maxAttempts
+     * @param mixed $strategy
+     * @param int $waitCap
+     * @param bool $useJitter
+     * @param callable $decider
+     */
+    public function __construct(
+        $maxAttempts = null,
+        $strategy = null,
+        $waitCap = null,
+        $useJitter = null,
+        $decider = null
+    ) {
+        $this->setMaxAttempts($maxAttempts ?: self::$defaultMaxAttempts);
+        $this->setStrategy($strategy ?: self::$defaultStrategy);
+        $this->setJitter($useJitter ?: self::$defaultJitterEnabled);
+        $this->setWaitCap($waitCap);
+        $this->setDecider($decider ?: $this->getDefaultDecider());
+    }
+
+    /**
+     * @param integer $attempts
+     */
+    public function setMaxAttempts($attempts)
+    {
+        $this->maxAttempts = $attempts;
+        
+         return $this;
+    }
+
+    /**
+     * @return integer
+     */
+    public function getMaxAttempts()
+    {
+        return $this->maxAttempts;
+    }
+
+    /**
+     * @param int|null $cap
+     *
+     * @return $this
+     */
+    public function setWaitCap($cap)
+    {
+        $this->waitCap = $cap;
+
+        return $this;
+    }
+
+    /**
+     * @return int|null
+     */
+    public function getWaitCap()
+    {
+        return $this->waitCap;
+    }
+
+    /**
+     * @param bool $useJitter
+     *
+     * @return $this
+     */
+    public function setJitter($useJitter)
+    {
+        $this->useJitter = $useJitter;
+
+        return $this;
+    }
+
+    /**
+     *
+     */
+    public function enableJitter()
+    {
+        $this->setJitter(true);
+
+        return $this;
+    }
+
+    /**
+     *
+     */
+    public function disableJitter()
+    {
+        $this->setJitter(false);
+
+        return $this;
+    }
+
+    public function jitterEnabled()
+    {
+        return $this->useJitter;
+    }
+
+    /**
+     * @return callable
+     */
+    public function getStrategy()
+    {
+        return $this->strategy;
+    }
+
+    /**
+     * @param mixed $strategy
+     *
+     * @return $this
+     */
+    public function setStrategy($strategy)
+    {
+        $this->strategy = $this->buildStrategy($strategy);
+
+        return $this;
+    }
+
+    /**
+     * Builds a callable strategy.
+     *
+     * @param mixed $strategy   Can be a string that matches a key in $strategies, an instance of AbstractStrategy
+     *                          (or any other instance that has an __invoke method), a callback function, or
+     *                          an integer (which we interpret to mean you want a ConstantStrategy)
+     *
+     * @return callable
+     */
+    protected function buildStrategy($strategy)
+    {
+        if (is_string($strategy) && array_key_exists($strategy, $this->strategies)) {
+            return new $this->strategies[$strategy];
+        }
+
+        if (is_callable($strategy)) {
+            return $strategy;
+        }
+
+        if (is_int($strategy)) {
+            return new ConstantStrategy($strategy);
+        }
+
+        throw new InvalidArgumentException("Invalid strategy: " . $strategy);
+    }
+
+    /**
+     * @param callable $callback
+     *
+     * @return mixed
+     * @throws Exception
+     */
+    public function run($callback)
+    {
+        $attempt = 0;
+        $try = true;
+
+        while ($try) {
+
+            $result = null;
+            $exception = null;
+
+            $this->wait($attempt);
+            try {
+                $result = call_user_func($callback);
+            } catch (\Throwable $e) {
+                if ($e instanceof \Error) {
+                    $e = new Exception($e->getMessage(), $e->getCode(), $e);
+                }
+                $this->exceptions[] = $e;
+                $exception = $e;
+            } catch (Exception $e) {
+                $this->exceptions[] = $e;
+                $exception = $e;
+            }
+            $try = call_user_func($this->decider, ++$attempt, $this->getMaxAttempts(), $result, $exception);
+
+            if($try && isset($this->errorHandler)) {
+                call_user_func($this->errorHandler, $exception, $attempt, $this->getMaxAttempts());
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Sets the decider callback
+     * @param callable $callback
+     * @return $this
+     */
+    public function setDecider($callback)
+    {
+        $this->decider = $callback;
+        return $this;
+    }
+
+    /**
+     * Sets the error handler callback
+     * @param callable $callback
+     * @return $this
+     */
+    public function setErrorHandler($callback)
+    {
+        $this->errorHandler = $callback;
+        return $this;
+    }
+
+    /**
+     * Gets a default decider that simply check exceptions and maxattempts
+     * @return \Closure
+     */
+    protected function getDefaultDecider()
+    {
+        return function ($retry, $maxAttempts, $result = null, $exception = null) {
+            if($retry >= $maxAttempts && ! is_null($exception)) {
+                throw  $exception;
+            }
+
+            return $retry < $maxAttempts && !is_null($exception);
+        };
+    }
+
+    /**
+     * @param int $attempt
+     */
+    public function wait($attempt)
+    {
+        if ($attempt == 0) {
+            return;
+        }
+
+        usleep($this->getWaitTime($attempt) * 1000);
+    }
+
+    /**
+     * @param int $attempt
+     *
+     * @return int
+     */
+    public function getWaitTime($attempt)
+    {
+        $waitTime = call_user_func($this->getStrategy(), $attempt);
+
+        return $this->jitter($this->cap($waitTime));
+    }
+
+    /**
+     * @param int $waitTime
+     *
+     * @return mixed
+     */
+    protected function cap($waitTime)
+    {
+        return is_int($this->getWaitCap())
+            ? min($this->getWaitCap(), $waitTime)
+            : $waitTime;
+    }
+
+    /**
+     * @param int $waitTime
+     *
+     * @return int
+     */
+    protected function jitter($waitTime)
+    {
+        return $this->jitterEnabled()
+            ? mt_rand(0, $waitTime)
+            : $waitTime;
+    }
+}

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