Quellcode durchsuchen

add spatie async framework - testing out for now

CauseFX vor 5 Jahren
Ursprung
Commit
ed9e608108
72 geänderte Dateien mit 7954 neuen und 8 gelöschten Zeilen
  1. 2 1
      api/composer.json
  2. 195 1
      api/composer.lock
  3. 29 2
      api/vendor/composer/InstalledVersions.php
  4. 3 1
      api/vendor/composer/autoload_files.php
  5. 3 0
      api/vendor/composer/autoload_psr4.php
  6. 18 1
      api/vendor/composer/autoload_static.php
  7. 203 0
      api/vendor/composer/installed.json
  8. 29 2
      api/vendor/composer/installed.php
  9. 256 0
      api/vendor/opis/closure/CHANGELOG.md
  10. 20 0
      api/vendor/opis/closure/LICENSE
  11. 9 0
      api/vendor/opis/closure/NOTICE
  12. 92 0
      api/vendor/opis/closure/README.md
  13. 39 0
      api/vendor/opis/closure/autoload.php
  14. 44 0
      api/vendor/opis/closure/composer.json
  15. 41 0
      api/vendor/opis/closure/functions.php
  16. 62 0
      api/vendor/opis/closure/src/Analyzer.php
  17. 34 0
      api/vendor/opis/closure/src/ClosureContext.php
  18. 25 0
      api/vendor/opis/closure/src/ClosureScope.php
  19. 99 0
      api/vendor/opis/closure/src/ClosureStream.php
  20. 25 0
      api/vendor/opis/closure/src/ISecurityProvider.php
  21. 1093 0
      api/vendor/opis/closure/src/ReflectionClosure.php
  22. 18 0
      api/vendor/opis/closure/src/SecurityException.php
  23. 42 0
      api/vendor/opis/closure/src/SecurityProvider.php
  24. 31 0
      api/vendor/opis/closure/src/SelfReference.php
  25. 678 0
      api/vendor/opis/closure/src/SerializableClosure.php
  26. 55 0
      api/vendor/spatie/async/.github/CONTRIBUTING.md
  27. 1 0
      api/vendor/spatie/async/.github/FUNDING.yml
  28. 3 0
      api/vendor/spatie/async/.github/SECURITY.md
  29. 23 0
      api/vendor/spatie/async/.github/workflows/php-cs-fixer.yml
  30. 39 0
      api/vendor/spatie/async/.github/workflows/run-tests.yml
  31. 43 0
      api/vendor/spatie/async/.php_cs.dist
  32. 63 0
      api/vendor/spatie/async/CHANGELOG.md
  33. 55 0
      api/vendor/spatie/async/CONTRIBUTING.md
  34. 21 0
      api/vendor/spatie/async/LICENSE.md
  35. 336 0
      api/vendor/spatie/async/README.md
  36. 5 0
      api/vendor/spatie/async/UPGRADING.md
  37. 51 0
      api/vendor/spatie/async/composer.json
  38. BIN
      api/vendor/spatie/async/docs/benchmarks.png
  39. 18 0
      api/vendor/spatie/async/src/Output/ParallelError.php
  40. 31 0
      api/vendor/spatie/async/src/Output/ParallelException.php
  41. 36 0
      api/vendor/spatie/async/src/Output/SerializableException.php
  42. 337 0
      api/vendor/spatie/async/src/Pool.php
  43. 56 0
      api/vendor/spatie/async/src/PoolStatus.php
  44. 131 0
      api/vendor/spatie/async/src/Process/ParallelProcess.php
  45. 108 0
      api/vendor/spatie/async/src/Process/ProcessCallbacks.php
  46. 52 0
      api/vendor/spatie/async/src/Process/Runnable.php
  47. 83 0
      api/vendor/spatie/async/src/Process/SynchronousProcess.php
  48. 45 0
      api/vendor/spatie/async/src/Runtime/ChildRuntime.php
  49. 107 0
      api/vendor/spatie/async/src/Runtime/ParentRuntime.php
  50. 17 0
      api/vendor/spatie/async/src/Task.php
  51. 24 0
      api/vendor/spatie/async/src/helpers.php
  52. 116 0
      api/vendor/symfony/process/CHANGELOG.md
  53. 21 0
      api/vendor/symfony/process/Exception/ExceptionInterface.php
  54. 21 0
      api/vendor/symfony/process/Exception/InvalidArgumentException.php
  55. 21 0
      api/vendor/symfony/process/Exception/LogicException.php
  56. 54 0
      api/vendor/symfony/process/Exception/ProcessFailedException.php
  57. 41 0
      api/vendor/symfony/process/Exception/ProcessSignaledException.php
  58. 69 0
      api/vendor/symfony/process/Exception/ProcessTimedOutException.php
  59. 21 0
      api/vendor/symfony/process/Exception/RuntimeException.php
  60. 86 0
      api/vendor/symfony/process/ExecutableFinder.php
  61. 93 0
      api/vendor/symfony/process/InputStream.php
  62. 19 0
      api/vendor/symfony/process/LICENSE
  63. 99 0
      api/vendor/symfony/process/PhpExecutableFinder.php
  64. 72 0
      api/vendor/symfony/process/PhpProcess.php
  65. 178 0
      api/vendor/symfony/process/Pipes/AbstractPipes.php
  66. 61 0
      api/vendor/symfony/process/Pipes/PipesInterface.php
  67. 163 0
      api/vendor/symfony/process/Pipes/UnixPipes.php
  68. 204 0
      api/vendor/symfony/process/Pipes/WindowsPipes.php
  69. 1674 0
      api/vendor/symfony/process/Process.php
  70. 69 0
      api/vendor/symfony/process/ProcessUtils.php
  71. 13 0
      api/vendor/symfony/process/README.md
  72. 29 0
      api/vendor/symfony/process/composer.json

+ 2 - 1
api/composer.json

@@ -16,6 +16,7 @@
     "slim/psr7": "^1.1",
     "zircote/swagger-php": "^3.0",
     "bogstag/oauth2-trakt": "^1.0",
-    "paquettg/php-html-parser": "^3.1"
+    "paquettg/php-html-parser": "^3.1",
+    "spatie/async": "^1.5"
   }
 }

+ 195 - 1
api/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": "6c33c6a831d0034c65fe02a8df137f7c",
+    "content-hash": "f3f5151b2f339d3eb4ae962abf24a3ff",
     "packages": [
         {
             "name": "adldap2/adldap2",
@@ -1045,6 +1045,71 @@
             ],
             "time": "2018-02-13T20:26:39+00:00"
         },
+        {
+            "name": "opis/closure",
+            "version": "3.6.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/opis/closure.git",
+                "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5",
+                "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.4 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "jeremeamia/superclosure": "^2.0",
+                "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.6.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Opis\\Closure\\": "src/"
+                },
+                "files": [
+                    "functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marius Sarca",
+                    "email": "marius.sarca@gmail.com"
+                },
+                {
+                    "name": "Sorin Sarca",
+                    "email": "sarca_sorin@hotmail.com"
+                }
+            ],
+            "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
+            "homepage": "https://opis.io/closure",
+            "keywords": [
+                "anonymous functions",
+                "closure",
+                "function",
+                "serializable",
+                "serialization",
+                "serialize"
+            ],
+            "support": {
+                "issues": "https://github.com/opis/closure/issues",
+                "source": "https://github.com/opis/closure/tree/3.6.1"
+            },
+            "time": "2020-11-07T02:01:34+00:00"
+        },
         {
             "name": "paquettg/php-html-parser",
             "version": "3.1.1",
@@ -2354,6 +2419,73 @@
             ],
             "time": "2020-12-01T19:41:22+00:00"
         },
+        {
+            "name": "spatie/async",
+            "version": "1.5.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/async.git",
+                "reference": "d371b76ff876530c4906596490fd977720477e48"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/async/zipball/d371b76ff876530c4906596490fd977720477e48",
+                "reference": "d371b76ff876530c4906596490fd977720477e48",
+                "shasum": ""
+            },
+            "require": {
+                "opis/closure": "^3.4.2",
+                "php": "^7.1 || ^8.0",
+                "symfony/process": "^3.3 || ^4.0 || ^5.0"
+            },
+            "require-dev": {
+                "larapack/dd": "^1.1",
+                "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0",
+                "symfony/stopwatch": "^4.0 || ^5.0"
+            },
+            "suggest": {
+                "ext-pcntl": "Required to use async processes",
+                "ext-posix": "Required to use async processes"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/helpers.php"
+                ],
+                "psr-4": {
+                    "Spatie\\Async\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Brent Roose",
+                    "email": "brent@spatie.be",
+                    "homepage": "https://spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Asynchronous and parallel PHP with the PCNTL extension",
+            "homepage": "https://github.com/spatie/async",
+            "keywords": [
+                "async",
+                "spatie"
+            ],
+            "support": {
+                "issues": "https://github.com/spatie/async/issues",
+                "source": "https://github.com/spatie/async/tree/1.5.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/spatie",
+                    "type": "github"
+                }
+            ],
+            "time": "2020-12-29T22:19:12+00:00"
+        },
         {
             "name": "symfony/deprecation-contracts",
             "version": "v2.1.3",
@@ -2820,6 +2952,68 @@
             ],
             "time": "2018-08-06T14:22:27+00:00"
         },
+        {
+            "name": "symfony/process",
+            "version": "v5.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/process.git",
+                "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f",
+                "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-php80": "^1.15"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Process\\": ""
+                },
+                "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": "Executes commands in sub-processes",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/process/tree/v5.2.4"
+            },
+            "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-01-27T10:15:41+00:00"
+        },
         {
             "name": "symfony/var-dumper",
             "version": "v4.2.3",

+ 29 - 2
api/vendor/composer/InstalledVersions.php

@@ -29,7 +29,7 @@ private static $installed = array (
     'aliases' => 
     array (
     ),
-    'reference' => '77336ee3aa0cae2c7d4db514402d14eee0792e88',
+    'reference' => '97eb7ed7197678decba9c8f766af5558dda95fc6',
     'name' => '__root__',
   ),
   'versions' => 
@@ -41,7 +41,7 @@ private static $installed = array (
       'aliases' => 
       array (
       ),
-      'reference' => '77336ee3aa0cae2c7d4db514402d14eee0792e88',
+      'reference' => '97eb7ed7197678decba9c8f766af5558dda95fc6',
     ),
     'adldap2/adldap2' => 
     array (
@@ -212,6 +212,15 @@ private static $installed = array (
       ),
       'reference' => '181d480e08d9476e61381e04a71b34dc0432e812',
     ),
+    'opis/closure' => 
+    array (
+      'pretty_version' => '3.6.1',
+      'version' => '3.6.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5',
+    ),
     'paquettg/php-html-parser' => 
     array (
       'pretty_version' => '3.1.1',
@@ -431,6 +440,15 @@ private static $installed = array (
       ),
       'reference' => '0905e0775f8c1cfb3bbcfabeb6588dcfd8b82d3f',
     ),
+    'spatie/async' => 
+    array (
+      'pretty_version' => '1.5.3',
+      'version' => '1.5.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd371b76ff876530c4906596490fd977720477e48',
+    ),
     'symfony/deprecation-contracts' => 
     array (
       'pretty_version' => 'v2.1.3',
@@ -503,6 +521,15 @@ private static $installed = array (
       ),
       'reference' => '8e15d04ba3440984d23e7964b2ee1d25c8de1581',
     ),
+    'symfony/process' => 
+    array (
+      'pretty_version' => 'v5.2.4',
+      'version' => '5.2.4.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '313a38f09c77fbcdc1d223e57d368cea76a2fd2f',
+    ),
     'symfony/var-dumper' => 
     array (
       'pretty_version' => 'v4.2.3',

+ 3 - 1
api/vendor/composer/autoload_files.php

@@ -9,18 +9,20 @@ return array(
     '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
     'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
     'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
+    'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
     '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
     '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
     '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
     '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
     '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
-    'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
     '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
     '253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php',
     '3109cb1a231dcd04bee1f9f620d46975' => $vendorDir . '/paragonie/sodium_compat/autoload.php',
     'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php',
     'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php',
     'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php',
+    '538ca81a9a966a6716601ecf48f4eaef' => $vendorDir . '/opis/closure/functions.php',
     '0097ca414fcb37c7130ac24b05f485f8' => $vendorDir . '/dibi/dibi/src/loader.php',
     '0ccdf99b8f62f02c52cba55802e0c2e7' => $vendorDir . '/zircote/swagger-php/src/functions.php',
+    '7dd996d98a91d095f100c75e0b9e2391' => $vendorDir . '/spatie/async/src/helpers.php',
 );

+ 3 - 0
api/vendor/composer/autoload_psr4.php

@@ -15,7 +15,9 @@ return array(
     'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
     'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
     'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
+    'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
     'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
+    'Spatie\\Async\\' => array($vendorDir . '/spatie/async/src'),
     'Slim\\Psr7\\' => array($vendorDir . '/slim/psr7/src'),
     'Slim\\' => array($vendorDir . '/slim/slim/Slim'),
     'Pusher\\' => array($vendorDir . '/pusher/pusher-php-server/src'),
@@ -30,6 +32,7 @@ return array(
     'ParagonIE\\ConstantTime\\' => array($vendorDir . '/paragonie/constant_time_encoding/src'),
     'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
     'PHPHtmlParser\\' => array($vendorDir . '/paquettg/php-html-parser/src/PHPHtmlParser'),
+    'Opis\\Closure\\' => array($vendorDir . '/opis/closure/src'),
     'OpenApi\\' => array($vendorDir . '/zircote/swagger-php/src'),
     'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'),
     'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src'),

+ 18 - 1
api/vendor/composer/autoload_static.php

@@ -10,20 +10,22 @@ class ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb
         '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
         'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
         'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
+        'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
         '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
         '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
         '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
         '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
         '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
-        'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
         '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
         '253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php',
         '3109cb1a231dcd04bee1f9f620d46975' => __DIR__ . '/..' . '/paragonie/sodium_compat/autoload.php',
         'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php',
         'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php',
         'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php',
+        '538ca81a9a966a6716601ecf48f4eaef' => __DIR__ . '/..' . '/opis/closure/functions.php',
         '0097ca414fcb37c7130ac24b05f485f8' => __DIR__ . '/..' . '/dibi/dibi/src/loader.php',
         '0ccdf99b8f62f02c52cba55802e0c2e7' => __DIR__ . '/..' . '/zircote/swagger-php/src/functions.php',
+        '7dd996d98a91d095f100c75e0b9e2391' => __DIR__ . '/..' . '/spatie/async/src/helpers.php',
     );
 
     public static $prefixLengthsPsr4 = array (
@@ -41,7 +43,9 @@ class ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb
             'Symfony\\Polyfill\\Ctype\\' => 23,
             'Symfony\\Component\\Yaml\\' => 23,
             'Symfony\\Component\\VarDumper\\' => 28,
+            'Symfony\\Component\\Process\\' => 26,
             'Symfony\\Component\\Finder\\' => 25,
+            'Spatie\\Async\\' => 13,
             'Slim\\Psr7\\' => 10,
             'Slim\\' => 5,
         ),
@@ -62,6 +66,7 @@ class ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb
         ),
         'O' => 
         array (
+            'Opis\\Closure\\' => 13,
             'OpenApi\\' => 8,
         ),
         'M' => 
@@ -155,10 +160,18 @@ class ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb
         array (
             0 => __DIR__ . '/..' . '/symfony/var-dumper',
         ),
+        'Symfony\\Component\\Process\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/process',
+        ),
         'Symfony\\Component\\Finder\\' => 
         array (
             0 => __DIR__ . '/..' . '/symfony/finder',
         ),
+        'Spatie\\Async\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/spatie/async/src',
+        ),
         'Slim\\Psr7\\' => 
         array (
             0 => __DIR__ . '/..' . '/slim/psr7/src',
@@ -217,6 +230,10 @@ class ComposerStaticInitcbdc783d76f8e7563dcce7d8af053ecb
         array (
             0 => __DIR__ . '/..' . '/paquettg/php-html-parser/src/PHPHtmlParser',
         ),
+        'Opis\\Closure\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/opis/closure/src',
+        ),
         'OpenApi\\' => 
         array (
             0 => __DIR__ . '/..' . '/zircote/swagger-php/src',

+ 203 - 0
api/vendor/composer/installed.json

@@ -1093,6 +1093,74 @@
             ],
             "install-path": "../nikic/fast-route"
         },
+        {
+            "name": "opis/closure",
+            "version": "3.6.1",
+            "version_normalized": "3.6.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/opis/closure.git",
+                "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/opis/closure/zipball/943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5",
+                "reference": "943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.4 || ^7.0 || ^8.0"
+            },
+            "require-dev": {
+                "jeremeamia/superclosure": "^2.0",
+                "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+            },
+            "time": "2020-11-07T02:01:34+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.6.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Opis\\Closure\\": "src/"
+                },
+                "files": [
+                    "functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marius Sarca",
+                    "email": "marius.sarca@gmail.com"
+                },
+                {
+                    "name": "Sorin Sarca",
+                    "email": "sarca_sorin@hotmail.com"
+                }
+            ],
+            "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
+            "homepage": "https://opis.io/closure",
+            "keywords": [
+                "anonymous functions",
+                "closure",
+                "function",
+                "serializable",
+                "serialization",
+                "serialize"
+            ],
+            "support": {
+                "issues": "https://github.com/opis/closure/issues",
+                "source": "https://github.com/opis/closure/tree/3.6.1"
+            },
+            "install-path": "../opis/closure"
+        },
         {
             "name": "paquettg/php-html-parser",
             "version": "3.1.1",
@@ -2468,6 +2536,76 @@
             ],
             "install-path": "../slim/slim"
         },
+        {
+            "name": "spatie/async",
+            "version": "1.5.3",
+            "version_normalized": "1.5.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/async.git",
+                "reference": "d371b76ff876530c4906596490fd977720477e48"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/async/zipball/d371b76ff876530c4906596490fd977720477e48",
+                "reference": "d371b76ff876530c4906596490fd977720477e48",
+                "shasum": ""
+            },
+            "require": {
+                "opis/closure": "^3.4.2",
+                "php": "^7.1 || ^8.0",
+                "symfony/process": "^3.3 || ^4.0 || ^5.0"
+            },
+            "require-dev": {
+                "larapack/dd": "^1.1",
+                "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0",
+                "symfony/stopwatch": "^4.0 || ^5.0"
+            },
+            "suggest": {
+                "ext-pcntl": "Required to use async processes",
+                "ext-posix": "Required to use async processes"
+            },
+            "time": "2020-12-29T22:19:12+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "src/helpers.php"
+                ],
+                "psr-4": {
+                    "Spatie\\Async\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Brent Roose",
+                    "email": "brent@spatie.be",
+                    "homepage": "https://spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Asynchronous and parallel PHP with the PCNTL extension",
+            "homepage": "https://github.com/spatie/async",
+            "keywords": [
+                "async",
+                "spatie"
+            ],
+            "support": {
+                "issues": "https://github.com/spatie/async/issues",
+                "source": "https://github.com/spatie/async/tree/1.5.3"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/spatie",
+                    "type": "github"
+                }
+            ],
+            "install-path": "../spatie/async"
+        },
         {
             "name": "symfony/deprecation-contracts",
             "version": "v2.1.3",
@@ -2958,6 +3096,71 @@
             ],
             "install-path": "../symfony/polyfill-util"
         },
+        {
+            "name": "symfony/process",
+            "version": "v5.2.4",
+            "version_normalized": "5.2.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/process.git",
+                "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f",
+                "reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.2.5",
+                "symfony/polyfill-php80": "^1.15"
+            },
+            "time": "2021-01-27T10:15:41+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Process\\": ""
+                },
+                "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": "Executes commands in sub-processes",
+            "homepage": "https://symfony.com",
+            "support": {
+                "source": "https://github.com/symfony/process/tree/v5.2.4"
+            },
+            "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/process"
+        },
         {
             "name": "symfony/var-dumper",
             "version": "v4.2.3",

+ 29 - 2
api/vendor/composer/installed.php

@@ -6,7 +6,7 @@
     'aliases' => 
     array (
     ),
-    'reference' => '77336ee3aa0cae2c7d4db514402d14eee0792e88',
+    'reference' => '97eb7ed7197678decba9c8f766af5558dda95fc6',
     'name' => '__root__',
   ),
   'versions' => 
@@ -18,7 +18,7 @@
       'aliases' => 
       array (
       ),
-      'reference' => '77336ee3aa0cae2c7d4db514402d14eee0792e88',
+      'reference' => '97eb7ed7197678decba9c8f766af5558dda95fc6',
     ),
     'adldap2/adldap2' => 
     array (
@@ -189,6 +189,15 @@
       ),
       'reference' => '181d480e08d9476e61381e04a71b34dc0432e812',
     ),
+    'opis/closure' => 
+    array (
+      'pretty_version' => '3.6.1',
+      'version' => '3.6.1.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '943b5d70cc5ae7483f6aff6ff43d7e34592ca0f5',
+    ),
     'paquettg/php-html-parser' => 
     array (
       'pretty_version' => '3.1.1',
@@ -408,6 +417,15 @@
       ),
       'reference' => '0905e0775f8c1cfb3bbcfabeb6588dcfd8b82d3f',
     ),
+    'spatie/async' => 
+    array (
+      'pretty_version' => '1.5.3',
+      'version' => '1.5.3.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => 'd371b76ff876530c4906596490fd977720477e48',
+    ),
     'symfony/deprecation-contracts' => 
     array (
       'pretty_version' => 'v2.1.3',
@@ -480,6 +498,15 @@
       ),
       'reference' => '8e15d04ba3440984d23e7964b2ee1d25c8de1581',
     ),
+    'symfony/process' => 
+    array (
+      'pretty_version' => 'v5.2.4',
+      'version' => '5.2.4.0',
+      'aliases' => 
+      array (
+      ),
+      'reference' => '313a38f09c77fbcdc1d223e57d368cea76a2fd2f',
+    ),
     'symfony/var-dumper' => 
     array (
       'pretty_version' => 'v4.2.3',

+ 256 - 0
api/vendor/opis/closure/CHANGELOG.md

@@ -0,0 +1,256 @@
+CHANGELOG
+---------
+
+### v3.6.1, 2020.11.07
+
+- Fixed serialization error [#84](https://github.com/opis/closure/issues/84)
+
+### v3.6.0, 2020.10.12
+
+- Initial PHP 8 Support [#67](https://github.com/opis/closure/issues/67).
+
+### v3.5.7, 2020.09.06
+
+- Fixed issue [#76](https://github.com/opis/closure/issues/76).
+- Fixed issue [#78](https://github.com/opis/closure/issues/78).
+
+### v3.5.6, 2020.08.11
+
+- Fixed issue [#70](https://github.com/opis/closure/issues/70)
+
+### v3.5.5, 2020.06.17
+
+- Fixed a false-positive when using `Opis\Closure\ReflectionClosure::isScopeRequired` method
+
+### v3.5.4, 2020.06.07
+
+- Fixed a false-positive when using `Opis\Closure\ReflectionClosure::isScopeRequired` method
+- Fixed a bug related to `T_STRING_VARNAME`
+
+### v3.5.3, 2020.05.25
+
+- Improved parser
+- The class scope optimisation is no longer used. We always bind now to the closure's original class scope.
+When the class scope was `null`, the optimisation failed to work as expected and kept the wrong `SerializableClosure` scope.
+
+### v3.5.2, 2020.05.21
+
+- Removed extra semicolon in short closures, since is not part of the closure's body.
+
+### v3.5.1, 2019.11.30
+
+- Bugfix. See #47
+
+### v3.5.0, 2019.11.29
+
+- Added support for short closures (arrow functions)
+- Added `isShortClosure` method to `Opis\Closure\ReflectionClosure`
+
+### v3.4.2, 2019.11.29
+
+- Added `stream_set_option()`
+
+### v3.4.1, 2019.10.19
+
+- Fixed a [bug](https://github.com/opis/closure/issues/40) that prevented serialization to work correctly.
+
+### v3.4.0, 2019.09.03
+
+- Added `createClosure` static method in `Opis\Closure\SerializableClosure`.
+This method creates a new closure from arbitrary code, emulating `create_function`,
+but without using eval
+
+### v3.3.1, 2019.07.10
+
+- Use `sha1` instead of `md5` for hashing file names in `Opis\Closure\ReflectionClosure` class
+
+### v3.3.0, 2019.05.31
+
+- Fixed a bug that prevented signed closures to properly work when the serialized string
+contains invalid UTF-8 chars. Starting with this version `json_encode` is no longer used
+when signing a closure. Backward compatibility is maintained and all closures that were 
+previously signed using the old method will continue to work.
+
+### v3.2.0, 2019.05.05
+
+- Since an unsigned closure can be unserialized when no security provider is set, 
+there is no reason to treat differently a signed closure in the same situation.
+Therefore, the `Opis\Closure\SecurityException` exception  is no longer thrown when 
+unserializing a signed closure, if no security provider is set.
+
+### v3.1.6, 2019.02.22
+
+- Fixed a bug that occurred when trying to set properties of classes that were not defined in user-land.
+Those properties are now ignored.
+
+### v3.1.5, 2019.01.14
+
+- Improved parser
+
+### v3.1.4, 2019.01.14
+
+- Added support for static methods that are named using PHP keywords or magic constants.
+Ex: `A::new()`, `A::use()`, `A::if()`, `A::function()`, `A::__DIR__()`, etc.
+- Used `@internal` to mark classes & methods that are for internal use only and
+backward compatibility is not guaranteed.
+
+### v3.1.3, 2019.01.07
+
+- Fixed a bug that prevented traits to be correctly resolved when used by an
+anonymous class
+- Fixed a bug that occurred when `$this` keyword was used inside an anonymous class
+
+### v3.1.2, 2018.12.16
+
+* Fixed a bug regarding comma trail in group-use statements. See [issue 23](https://github.com/opis/closure/issues/23)
+
+### v3.1.1, 2018.10.02
+
+* Fixed a bug where `parent` keyword was treated like a class-name and scope was not added to the
+serialized closure
+* Fixed a bug where return type was not properly handled for nested closures
+* Support for anonymous classes was improved
+
+### v3.1.0, 2018.09.20
+
+* Added `transformUseVariables` and `resolveUseVariables` to
+`Opis\Closure\SerializableClosure` class.
+* Added `removeSecurityProvider` static method to 
+`Opis\Closure\SerializableClosure` class. 
+* Fixed some security related issues where a user was able to unserialize an unsigned
+closure, even when a security provider was in use.
+
+### v3.0.12, 2018.02.23
+
+* Bugfix. See [issue 20](https://github.com/opis/closure/issues/20)
+
+### v3.0.11, 2018.01.22
+
+* Bugfix. See [issue 18](https://github.com/opis/closure/issues/18)
+
+### v3.0.10, 2018.01.04
+
+* Improved support for PHP 7.1 & 7.2
+
+### v3.0.9, 2018.01.04
+
+* Fixed a bug where the return type was not properly resolved. 
+See [issue 17](https://github.com/opis/closure/issues/17)
+* Added more tests
+
+### v3.0.8, 2017.12.18
+
+* Fixed a bug. See [issue 16](https://github.com/opis/closure/issues/16)
+
+### v3.0.7, 2017.10.31
+
+* Bugfix: static properties are ignored now, since they are not serializable
+
+### v3.0.6, 2017.10.06
+
+* Fixed a bug introduced by accident in 3.0.5
+
+### v3.0.5, 2017.09.18
+
+* Fixed a bug related to nested references
+
+### v3.0.4, 2017.09.18
+
+* \[*internal*\] Refactored `SerializableClosure::mapPointers` method
+* \[*internal*\] Added a new optional argument to `SerializableClosure::unwrapClosures`
+* \[*internal*\] Removed `SerializableClosure::getClosurePointer` method
+* Fixed various bugs
+
+### v3.0.3, 2017.09.06
+
+* Fixed a bug related to nested object references 
+* \[*internal*\] `Opis\Closure\ClosureScope` now extends `SplObjectStorage`
+* \[*internal*\] The `storage` property was removed from `Opis\Closure\ClosureScope`
+* \[*internal*\] The `instances` and `objects` properties were removed from `Opis\Closure\ClosureContext`
+
+### v3.0.2, 2017.08.28
+
+* Fixed a bug where `$this` object was not handled properly inside the 
+`SerializableClosre::serialize` method. 
+
+### v3.0.1, 2017.04.13
+
+* Fixed a bug in 'ignore_next' state
+
+### v3.0.0, 2017.04.07
+
+* Dropped PHP 5.3 support
+* Moved source files from `lib` to `src` folder
+* Removed second parameter from `Opis\Closure\SerializableClosure::from` method and from constructor
+* Removed `Opis\Closure\{SecurityProviderInterface, DefaultSecurityProvider, SecureClosure}` classes
+* Refactored how signed closures were handled
+* Added `wrapClosures` and `unwrapClosures` static methods to `Opis\Closure\SerializableClosure` class
+* Added `Opis\Colosure\serialize` and `Opis\Closure\unserialize` functions
+* Improved serialization. You can now serialize arbitrary objects and the library will automatically wrap all closures
+
+### v2.4.0, 2016.12.16
+
+* The parser was refactored and improved
+* Refactored `Opis\Closure\SerializableClosure::__invoke` method
+* `Opis\Closure\{ISecurityProvider, SecurityProvider}` were added
+* `Opis\Closure\{SecurityProviderInterface, DefaultSecurityProvider, SecureClosure}` were deprecated
+and they will be removed in the next major version
+* `setSecretKey` and `addSecurityProvider` static methods were added to `Opis\Closure\SerializableClosure`
+
+### v2.3.2, 2016.12.15
+
+* Fixed a bug that prevented namespace resolution to be done properly
+
+### v2.3.1, 2016.12.13
+
+* Hotfix. See [PR](https://github.com/opis/closure/pull/7)
+
+### v2.3.0, 2016.11.17
+
+* Added `isBindingRequired` and `isScopeRequired` to the `Opis\Closure\ReflectionClosure` class
+* Automatically detects when the scope and/or the bound object of a closure needs to be serialized.
+
+### v2.2.1, 2016.08.20
+
+* Fixed a bug in `Opis\Closure\ReflectionClosure::fetchItems`
+
+### v2.2.0, 2016.07.26
+
+* Fixed CS
+* `Opis\Closure\ClosureContext`, `Opis\Closure\ClosureScope`, `Opis\Closure\SelfReference`
+ and `Opis\Closure\SecurityException` classes were moved into separate files
+* Added support for PHP7 syntax
+* Fixed some bugs in `Opis\Closure\ReflectionClosure` class
+* Improved closure parser
+* Added an analyzer for SuperClosure library
+
+### v2.1.0, 2015.09.30
+
+* Added support for the missing `__METHOD__`, `__FUNCTION__` and `__TRAIT__` magic constants
+* Added some security related classes and interfaces: `Opis\Closure\SecurityProviderInterface`,
+`Opis\Closure\DefaultSecurityProvider`, `Opis\Closure\SecureClosure`, `Opis\Closure\SecurityException`.
+* Fiexed a bug in `Opis\Closure\ReflectionClosure::getClasses` method
+* Other minor bugfixes
+* Added support for static closures
+* Added public `isStatic` method to `Opis\Closure\ReflectionClosure` class
+
+
+### v2.0.1, 2015.09.23
+
+* Removed `branch-alias` property from `composer.json`
+* Bugfix. See [issue #6](https://github.com/opis/closure/issues/6)
+
+### v2.0.0, 2015.07.31
+
+* The closure parser was improved
+* Class names are now automatically resolved
+* Added support for the `#trackme` directive which allows tracking closure's residing source
+
+### v1.3.0, 2014.10.18
+
+* Added autoload file
+* Changed README file
+
+### Opis Closure 1.2.2
+
+* Started changelog

+ 20 - 0
api/vendor/opis/closure/LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018-2019 Zindex Software
+
+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.

+ 9 - 0
api/vendor/opis/closure/NOTICE

@@ -0,0 +1,9 @@
+Opis Closure
+Copyright 2018-2019 Zindex Software
+
+This product includes software developed at
+Zindex Software (http://zindex.software).
+
+This software was originally developed by Marius Sarca and Sorin Sarca
+(Copyright 2014-2018). The copyright info was changed with the permission
+of the original authors.

+ 92 - 0
api/vendor/opis/closure/README.md

@@ -0,0 +1,92 @@
+Opis Closure
+====================
+[![Tests](https://github.com/opis/closure/workflows/Tests/badge.svg)](https://github.com/opis/closure/actions)
+[![Latest Stable Version](https://poser.pugx.org/opis/closure/v/stable.png)](https://packagist.org/packages/opis/closure)
+[![Latest Unstable Version](https://poser.pugx.org/opis/closure/v/unstable.png)](https://packagist.org/packages/opis/closure)
+[![License](https://poser.pugx.org/opis/closure/license.png)](https://packagist.org/packages/opis/closure)
+
+Serializable closures
+---------------------
+**Opis Closure** is a library that aims to overcome PHP's limitations regarding closure
+serialization by providing a wrapper that will make all closures serializable. 
+
+**The library's key features:**
+
+- Serialize any closure
+- Serialize arbitrary objects
+- Doesn't use `eval` for closure serialization or unserialization
+- Works with any PHP version that has support for closures
+- Supports PHP 7 syntax
+- Handles all variables referenced/imported in `use()` and automatically wraps all referenced/imported closures for
+proper serialization
+- Handles recursive closures
+- Handles magic constants like `__FILE__`, `__DIR__`, `__LINE__`, `__NAMESPACE__`, `__CLASS__`,
+`__TRAIT__`, `__METHOD__` and `__FUNCTION__`.
+- Automatically resolves all class names, function names and constant names used inside the closure
+- Track closure's residing source by using the `#trackme` directive
+- Simple and very fast parser
+- Any error or exception, that might occur when executing an unserialized closure, can be caught and treated properly
+- You can serialize/unserialize any closure unlimited times, even those previously unserialized
+(this is possible because `eval()` is not used for unserialization)
+- Handles static closures
+- Supports cryptographically signed closures
+- Provides a reflector that can give you information about the serialized closure
+- Provides an analyzer for *SuperClosure* library
+- Automatically detects when the scope and/or the bound object of a closure needs to be serialized
+in order for the closure to work after deserialization
+
+## Documentation
+
+The full documentation for this library can be found [here][documentation].
+
+## License
+
+**Opis Closure** is licensed under the [MIT License (MIT)][license].
+
+## Requirements
+
+* PHP ^5.4 || ^7.0
+
+## Installation
+
+**Opis Closure** is available on [Packagist] and it can be installed from a 
+command line interface by using [Composer]. 
+
+```bash
+composer require opis/closure
+```
+
+Or you could directly reference it into your `composer.json` file as a dependency
+
+```json
+{
+    "require": {
+        "opis/closure": "^3.5"
+    }
+}
+```
+
+### Migrating from 2.x
+
+If your project needs to support PHP 5.3 you can continue using the `2.x` version
+of **Opis Closure**. Otherwise, assuming you are not using one of the removed/refactored classes or features(see 
+[CHANGELOG]), migrating to version `3.x` is simply a matter of updating your `composer.json` file. 
+
+### Semantic versioning
+
+**Opis Closure** follows [semantic versioning][SemVer] specifications.
+
+### Arbitrary object serialization
+
+We've added this feature in order to be able to support the serialization of a closure's bound object. 
+The implementation is far from being perfect, and it's really hard to make it work flawless. 
+We will try to improve this, but we can't guarantee anything. 
+So our advice regarding the `Opis\Closure\serialize|unserialize` functions is to use them with caution.
+
+
+[documentation]: https://www.opis.io/closure "Opis Closure"
+[license]: http://opensource.org/licenses/MIT "MIT License"
+[Packagist]: https://packagist.org/packages/opis/closure "Packagist"
+[Composer]: https://getcomposer.org "Composer"
+[SemVer]: http://semver.org/ "Semantic versioning"
+[CHANGELOG]: https://github.com/opis/closure/blob/master/CHANGELOG.md "Changelog"

+ 39 - 0
api/vendor/opis/closure/autoload.php

@@ -0,0 +1,39 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+require_once 'functions.php';
+
+spl_autoload_register(function($class){
+   
+    $class = ltrim($class, '\\');
+    $dir = __DIR__ . '/src';
+    $namespace = 'Opis\Closure';
+    
+    if(strpos($class, $namespace) === 0)
+    {
+        $class = substr($class, strlen($namespace));
+        $path = '';
+        if(($pos = strripos($class, '\\')) !== FALSE)
+        {
+            $path = str_replace('\\', '/', substr($class, 0, $pos)) . '/';
+            $class = substr($class, $pos + 1);
+        }
+        $path .= str_replace('_', '/', $class) . '.php';
+        $dir .= '/' . $path;
+        
+        if(file_exists($dir))
+        {
+            include $dir;
+            return true;
+        }
+        
+        return false;
+    }
+    
+    return false;
+
+});

+ 44 - 0
api/vendor/opis/closure/composer.json

@@ -0,0 +1,44 @@
+{
+    "name": "opis/closure",
+    "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.",
+    "keywords": ["closure", "serialization", "function", "serializable", "serialize", "anonymous functions"],
+    "homepage": "https://opis.io/closure",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Marius Sarca",
+            "email": "marius.sarca@gmail.com"
+        },
+        {
+            "name": "Sorin Sarca",
+            "email": "sarca_sorin@hotmail.com"
+        }
+    ],
+    "require": {
+        "php": "^5.4 || ^7.0 || ^8.0"
+    },
+    "require-dev": {
+        "jeremeamia/superclosure": "^2.0",
+        "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "Opis\\Closure\\": "src/"
+        },
+        "files": ["functions.php"]
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Opis\\Closure\\Test\\": "tests/"
+        }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "3.6.x-dev"
+        }
+    },
+    "config": {
+        "preferred-install": "dist",
+        "sort-packages": true
+    }
+}

+ 41 - 0
api/vendor/opis/closure/functions.php

@@ -0,0 +1,41 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+/**
+ * Serialize
+ *
+ * @param mixed $data
+ * @return string
+ */
+function serialize($data)
+{
+    SerializableClosure::enterContext();
+    SerializableClosure::wrapClosures($data);
+    $data = \serialize($data);
+    SerializableClosure::exitContext();
+    return $data;
+}
+
+/**
+ * Unserialize
+ *
+ * @param string $data
+ * @param array|null $options
+ * @return mixed
+ */
+function unserialize($data, array $options = null)
+{
+    SerializableClosure::enterContext();
+    $data = ($options === null || \PHP_MAJOR_VERSION < 7)
+        ? \unserialize($data)
+        : \unserialize($data, $options);
+    SerializableClosure::unwrapClosures($data);
+    SerializableClosure::exitContext();
+    return $data;
+}

+ 62 - 0
api/vendor/opis/closure/src/Analyzer.php

@@ -0,0 +1,62 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+use Closure;
+use SuperClosure\Analyzer\ClosureAnalyzer;
+
+/**
+ * @deprecated We'll remove this class
+ */
+class Analyzer extends ClosureAnalyzer
+{
+    /**
+     * Analyzer a given closure.
+     *
+     * @param Closure $closure
+     *
+     * @return array
+     */
+    public function analyze(Closure $closure)
+    {
+        $reflection = new ReflectionClosure($closure);
+        $scope = $reflection->getClosureScopeClass();
+
+        $data = [
+            'reflection' => $reflection,
+            'code'       => $reflection->getCode(),
+            'hasThis'    => $reflection->isBindingRequired(),
+            'context'    => $reflection->getUseVariables(),
+            'hasRefs'    => false,
+            'binding'    => $reflection->getClosureThis(),
+            'scope'      => $scope ? $scope->getName() : null,
+            'isStatic'   => $reflection->isStatic(),
+        ];
+
+        return $data;
+    }
+
+    /**
+     * @param array $data
+     * @return mixed
+     */
+    protected function determineCode(array &$data)
+    {
+        return null;
+    }
+
+    /**
+     * @param array $data
+     * @return mixed
+     */
+    protected function determineContext(array &$data)
+    {
+        return null;
+    }
+
+}

+ 34 - 0
api/vendor/opis/closure/src/ClosureContext.php

@@ -0,0 +1,34 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+/**
+ * Closure context class
+ * @internal
+ */
+class ClosureContext
+{
+    /**
+     * @var ClosureScope Closures scope
+     */
+    public $scope;
+
+    /**
+     * @var integer
+     */
+    public $locks;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->scope = new ClosureScope();
+        $this->locks = 0;
+    }
+}

+ 25 - 0
api/vendor/opis/closure/src/ClosureScope.php

@@ -0,0 +1,25 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+/**
+ * Closure scope class
+ * @internal
+ */
+class ClosureScope extends \SplObjectStorage
+{
+    /**
+     * @var integer Number of serializations in current scope
+     */
+    public $serializations = 0;
+
+    /**
+     * @var integer Number of closures that have to be serialized
+     */
+    public $toserialize = 0;
+}

+ 99 - 0
api/vendor/opis/closure/src/ClosureStream.php

@@ -0,0 +1,99 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+/**
+ * @internal
+ */
+class ClosureStream
+{
+    const STREAM_PROTO = 'closure';
+
+    protected static $isRegistered = false;
+
+    protected $content;
+
+    protected $length;
+
+    protected $pointer = 0;
+
+    function stream_open($path, $mode, $options, &$opened_path)
+    {
+        $this->content = "<?php\nreturn " . substr($path, strlen(static::STREAM_PROTO . '://')) . ";";
+        $this->length = strlen($this->content);
+        return true;
+    }
+
+    public function stream_read($count)
+    {
+        $value = substr($this->content, $this->pointer, $count);
+        $this->pointer += $count;
+        return $value;
+    }
+
+    public function stream_eof()
+    {
+        return $this->pointer >= $this->length;
+    }
+
+    public function stream_set_option($option, $arg1, $arg2)
+    {
+        return false;
+    }
+
+    public function stream_stat()
+    {
+        $stat = stat(__FILE__);
+        $stat[7] = $stat['size'] = $this->length;
+        return $stat;
+    }
+
+    public function url_stat($path, $flags)
+    {
+        $stat = stat(__FILE__);
+        $stat[7] = $stat['size'] = $this->length;
+        return $stat;
+    }
+
+    public function stream_seek($offset, $whence = SEEK_SET)
+    {
+        $crt = $this->pointer;
+
+        switch ($whence) {
+            case SEEK_SET:
+                $this->pointer = $offset;
+                break;
+            case SEEK_CUR:
+                $this->pointer += $offset;
+                break;
+            case SEEK_END:
+                $this->pointer = $this->length + $offset;
+                break;
+        }
+
+        if ($this->pointer < 0 || $this->pointer >= $this->length) {
+            $this->pointer = $crt;
+            return false;
+        }
+
+        return true;
+    }
+
+    public function stream_tell()
+    {
+        return $this->pointer;
+    }
+
+    public static function register()
+    {
+        if (!static::$isRegistered) {
+            static::$isRegistered = stream_wrapper_register(static::STREAM_PROTO, __CLASS__);
+        }
+    }
+
+}

+ 25 - 0
api/vendor/opis/closure/src/ISecurityProvider.php

@@ -0,0 +1,25 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+interface ISecurityProvider
+{
+    /**
+     * Sign serialized closure
+     * @param string $closure
+     * @return array
+     */
+    public function sign($closure);
+
+    /**
+     * Verify signature
+     * @param array $data
+     * @return bool
+     */
+    public function verify(array $data);
+}

+ 1093 - 0
api/vendor/opis/closure/src/ReflectionClosure.php

@@ -0,0 +1,1093 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', -4);
+defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', -5);
+defined('T_FN') || define('T_FN', -6);
+
+use Closure;
+use ReflectionFunction;
+
+class ReflectionClosure extends ReflectionFunction
+{
+    protected $code;
+    protected $tokens;
+    protected $hashedName;
+    protected $useVariables;
+    protected $isStaticClosure;
+    protected $isScopeRequired;
+    protected $isBindingRequired;
+    protected $isShortClosure;
+
+    protected static $files = array();
+    protected static $classes = array();
+    protected static $functions = array();
+    protected static $constants = array();
+    protected static $structures = array();
+
+
+    /**
+     * ReflectionClosure constructor.
+     * @param Closure $closure
+     * @param string|null $code This is ignored. Do not use it
+     * @throws \ReflectionException
+     */
+    public function __construct(Closure $closure, $code = null)
+    {
+        parent::__construct($closure);
+    }
+
+    /**
+     * @return bool
+     */
+    public function isStatic()
+    {
+        if ($this->isStaticClosure === null) {
+            $this->isStaticClosure = strtolower(substr($this->getCode(), 0, 6)) === 'static';
+        }
+
+        return $this->isStaticClosure;
+    }
+
+    public function isShortClosure()
+    {
+        if ($this->isShortClosure === null) {
+            $code = $this->getCode();
+            if ($this->isStatic()) {
+                $code = substr($code, 6);
+            }
+            $this->isShortClosure = strtolower(substr(trim($code), 0, 2)) === 'fn';
+        }
+
+        return $this->isShortClosure;
+    }
+
+    /**
+     * @return string
+     */
+    public function getCode()
+    {
+        if($this->code !== null){
+            return $this->code;
+        }
+
+        $fileName = $this->getFileName();
+        $line = $this->getStartLine() - 1;
+
+        $className = null;
+
+        if (null !== $className = $this->getClosureScopeClass()) {
+            $className = '\\' . trim($className->getName(), '\\');
+        }
+
+        $builtin_types = self::getBuiltinTypes();
+        $class_keywords = ['self', 'static', 'parent'];
+
+        $ns = $this->getNamespaceName();
+        $nsf = $ns == '' ? '' : ($ns[0] == '\\' ? $ns : '\\' . $ns);
+
+        $_file = var_export($fileName, true);
+        $_dir = var_export(dirname($fileName), true);
+        $_namespace = var_export($ns, true);
+        $_class = var_export(trim($className, '\\'), true);
+        $_function = $ns . ($ns == '' ? '' : '\\') . '{closure}';
+        $_method = ($className == '' ? '' : trim($className, '\\') . '::') . $_function;
+        $_function = var_export($_function, true);
+        $_method = var_export($_method, true);
+        $_trait = null;
+
+        $tokens = $this->getTokens();
+        $state = $lastState = 'start';
+        $inside_structure = false;
+        $isShortClosure = false;
+        $inside_structure_mark = 0;
+        $open = 0;
+        $code = '';
+        $id_start = $id_start_ci = $id_name = $context = '';
+        $classes = $functions = $constants = null;
+        $use = array();
+        $lineAdd = 0;
+        $isUsingScope = false;
+        $isUsingThisObject = false;
+
+        for($i = 0, $l = count($tokens); $i < $l; $i++) {
+            $token = $tokens[$i];
+            switch ($state) {
+                case 'start':
+                    if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) {
+                        $code .= $token[1];
+                        $state = $token[0] === T_FUNCTION ? 'function' : 'static';
+                    } elseif ($token[0] === T_FN) {
+                        $isShortClosure = true;
+                        $code .= $token[1];
+                        $state = 'closure_args';
+                    }
+                    break;
+                case 'static':
+                    if ($token[0] === T_WHITESPACE || $token[0] === T_COMMENT || $token[0] === T_FUNCTION) {
+                        $code .= $token[1];
+                        if ($token[0] === T_FUNCTION) {
+                            $state = 'function';
+                        }
+                    } elseif ($token[0] === T_FN) {
+                        $isShortClosure = true;
+                        $code .= $token[1];
+                        $state = 'closure_args';
+                    } else {
+                        $code = '';
+                        $state = 'start';
+                    }
+                    break;
+                case 'function':
+                    switch ($token[0]){
+                        case T_STRING:
+                            $code = '';
+                            $state = 'named_function';
+                            break;
+                        case '(':
+                            $code .= '(';
+                            $state = 'closure_args';
+                            break;
+                        default:
+                            $code .= is_array($token) ? $token[1] : $token;
+                    }
+                    break;
+                case 'named_function':
+                    if($token[0] === T_FUNCTION || $token[0] === T_STATIC){
+                        $code = $token[1];
+                        $state = $token[0] === T_FUNCTION ? 'function' : 'static';
+                    } elseif ($token[0] === T_FN) {
+                        $isShortClosure = true;
+                        $code .= $token[1];
+                        $state = 'closure_args';
+                    }
+                    break;
+                case 'closure_args':
+                    switch ($token[0]){
+                        case T_NAME_QUALIFIED:
+                            list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
+                            $context = 'args';
+                            $state = 'id_name';
+                            $lastState = 'closure_args';
+                            break;
+                        case T_NS_SEPARATOR:
+                        case T_STRING:
+                            $id_start = $token[1];
+                            $id_start_ci = strtolower($id_start);
+                            $id_name = '';
+                            $context = 'args';
+                            $state = 'id_name';
+                            $lastState = 'closure_args';
+                            break;
+                        case T_USE:
+                            $code .= $token[1];
+                            $state = 'use';
+                            break;
+                        case T_DOUBLE_ARROW:
+                            $code .= $token[1];
+                            if ($isShortClosure) {
+                                $state = 'closure';
+                            }
+                            break;
+                        case ':':
+                            $code .= ':';
+                            $state = 'return';
+                            break;
+                        case '{':
+                            $code .= '{';
+                            $state = 'closure';
+                            $open++;
+                            break;
+                        default:
+                            $code .= is_array($token) ? $token[1] : $token;
+                    }
+                    break;
+                case 'use':
+                    switch ($token[0]){
+                        case T_VARIABLE:
+                            $use[] = substr($token[1], 1);
+                            $code .= $token[1];
+                            break;
+                        case '{':
+                            $code .= '{';
+                            $state = 'closure';
+                            $open++;
+                            break;
+                        case ':':
+                            $code .= ':';
+                            $state = 'return';
+                            break;
+                        default:
+                            $code .= is_array($token) ? $token[1] : $token;
+                            break;
+                    }
+                    break;
+                case 'return':
+                    switch ($token[0]){
+                        case T_WHITESPACE:
+                        case T_COMMENT:
+                        case T_DOC_COMMENT:
+                            $code .= $token[1];
+                            break;
+                        case T_NS_SEPARATOR:
+                        case T_STRING:
+                            $id_start = $token[1];
+                            $id_start_ci = strtolower($id_start);
+                            $id_name = '';
+                            $context = 'return_type';
+                            $state = 'id_name';
+                            $lastState = 'return';
+                            break 2;
+                        case T_NAME_QUALIFIED:
+                            list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
+                            $context = 'return_type';
+                            $state = 'id_name';
+                            $lastState = 'return';
+                            break 2;
+                        case T_DOUBLE_ARROW:
+                            $code .= $token[1];
+                            if ($isShortClosure) {
+                                $state = 'closure';
+                            }
+                            break;
+                        case '{':
+                            $code .= '{';
+                            $state = 'closure';
+                            $open++;
+                            break;
+                        default:
+                            $code .= is_array($token) ? $token[1] : $token;
+                            break;
+                    }
+                    break;
+                case 'closure':
+                    switch ($token[0]){
+                        case T_CURLY_OPEN:
+                        case T_DOLLAR_OPEN_CURLY_BRACES:
+                        case '{':
+                            $code .= '{';
+                            $open++;
+                            break;
+                        case '}':
+                            $code .= '}';
+                            if(--$open === 0 && !$isShortClosure){
+                                break 3;
+                            } elseif ($inside_structure) {
+                                $inside_structure = !($open === $inside_structure_mark);
+                            }
+                            break;
+                        case '(':
+                        case '[':
+                            $code .= $token[0];
+                            if ($isShortClosure) {
+                                $open++;
+                            }
+                            break;
+                        case ')':
+                        case ']':
+                            if ($isShortClosure) {
+                                if ($open === 0) {
+                                    break 3;
+                                }
+                                --$open;
+                            }
+                            $code .= $token[0];
+                            break;
+                        case ',':
+                        case ';':
+                            if ($isShortClosure && $open === 0) {
+                                break 3;
+                            }
+                            $code .= $token[0];
+                            break;
+                        case T_LINE:
+                            $code .= $token[2] - $line + $lineAdd;
+                            break;
+                        case T_FILE:
+                            $code .= $_file;
+                            break;
+                        case T_DIR:
+                            $code .= $_dir;
+                            break;
+                        case T_NS_C:
+                            $code .= $_namespace;
+                            break;
+                        case T_CLASS_C:
+                            $code .= $inside_structure ? $token[1] : $_class;
+                            break;
+                        case T_FUNC_C:
+                            $code .= $inside_structure ? $token[1] : $_function;
+                            break;
+                        case T_METHOD_C:
+                            $code .= $inside_structure ? $token[1] : $_method;
+                            break;
+                        case T_COMMENT:
+                            if (substr($token[1], 0, 8) === '#trackme') {
+                                $timestamp = time();
+                                $code .= '/**' . PHP_EOL;
+                                $code .= '* Date      : ' . date(DATE_W3C, $timestamp) . PHP_EOL;
+                                $code .= '* Timestamp : ' . $timestamp . PHP_EOL;
+                                $code .= '* Line      : ' . ($line + 1) . PHP_EOL;
+                                $code .= '* File      : ' . $_file . PHP_EOL . '*/' . PHP_EOL;
+                                $lineAdd += 5;
+                            } else {
+                                $code .= $token[1];
+                            }
+                            break;
+                        case T_VARIABLE:
+                            if($token[1] == '$this' && !$inside_structure){
+                                $isUsingThisObject = true;
+                            }
+                            $code .= $token[1];
+                            break;
+                        case T_STATIC:
+                        case T_NS_SEPARATOR:
+                        case T_STRING:
+                            $id_start = $token[1];
+                            $id_start_ci = strtolower($id_start);
+                            $id_name = '';
+                            $context = 'root';
+                            $state = 'id_name';
+                            $lastState = 'closure';
+                            break 2;
+                        case T_NAME_QUALIFIED:
+                            list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
+                            $context = 'root';
+                            $state = 'id_name';
+                            $lastState = 'closure';
+                            break 2;
+                        case T_NEW:
+                            $code .= $token[1];
+                            $context = 'new';
+                            $state = 'id_start';
+                            $lastState = 'closure';
+                            break 2;
+                        case T_USE:
+                            $code .= $token[1];
+                            $context = 'use';
+                            $state = 'id_start';
+                            $lastState = 'closure';
+                            break;
+                        case T_INSTANCEOF:
+                        case T_INSTEADOF:
+                            $code .= $token[1];
+                            $context = 'instanceof';
+                            $state = 'id_start';
+                            $lastState = 'closure';
+                            break;
+                        case T_OBJECT_OPERATOR:
+                        case T_DOUBLE_COLON:
+                            $code .= $token[1];
+                            $lastState = 'closure';
+                            $state = 'ignore_next';
+                            break;
+                        case T_FUNCTION:
+                            $code .= $token[1];
+                            $state = 'closure_args';
+                            if (!$inside_structure) {
+                                $inside_structure = true;
+                                $inside_structure_mark = $open;
+                            }
+                            break;
+                        case T_TRAIT_C:
+                            if ($_trait === null) {
+                                $startLine = $this->getStartLine();
+                                $endLine = $this->getEndLine();
+                                $structures = $this->getStructures();
+
+                                $_trait = '';
+
+                                foreach ($structures as &$struct) {
+                                    if ($struct['type'] === 'trait' &&
+                                        $struct['start'] <= $startLine &&
+                                        $struct['end'] >= $endLine
+                                    ) {
+                                        $_trait = ($ns == '' ? '' : $ns . '\\') . $struct['name'];
+                                        break;
+                                    }
+                                }
+
+                                $_trait = var_export($_trait, true);
+                            }
+
+                            $code .= $_trait;
+                            break;
+                        default:
+                            $code .= is_array($token) ? $token[1] : $token;
+                    }
+                    break;
+                case 'ignore_next':
+                    switch ($token[0]){
+                        case T_WHITESPACE:
+                        case T_COMMENT:
+                        case T_DOC_COMMENT:
+                            $code .= $token[1];
+                            break;
+                        case T_CLASS:
+                        case T_NEW:
+                        case T_STATIC:
+                        case T_VARIABLE:
+                        case T_STRING:
+                        case T_CLASS_C:
+                        case T_FILE:
+                        case T_DIR:
+                        case T_METHOD_C:
+                        case T_FUNC_C:
+                        case T_FUNCTION:
+                        case T_INSTANCEOF:
+                        case T_LINE:
+                        case T_NS_C:
+                        case T_TRAIT_C:
+                        case T_USE:
+                            $code .= $token[1];
+                            $state = $lastState;
+                            break;
+                        default:
+                            $state = $lastState;
+                            $i--;
+                    }
+                    break;
+                case 'id_start':
+                    switch ($token[0]){
+                        case T_WHITESPACE:
+                        case T_COMMENT:
+                        case T_DOC_COMMENT:
+                            $code .= $token[1];
+                            break;
+                        case T_NS_SEPARATOR:
+                        case T_NAME_FULLY_QUALIFIED:
+                        case T_STRING:
+                        case T_STATIC:
+                            $id_start = $token[1];
+                            $id_start_ci = strtolower($id_start);
+                            $id_name = '';
+                            $state = 'id_name';
+                            break 2;
+                        case T_NAME_QUALIFIED:
+                            list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
+                            $state = 'id_name';
+                            break 2;
+                        case T_VARIABLE:
+                            $code .= $token[1];
+                            $state = $lastState;
+                            break;
+                        case T_CLASS:
+                            $code .= $token[1];
+                            $state = 'anonymous';
+                            break;
+                        default:
+                            $i--;//reprocess last
+                            $state = 'id_name';
+                    }
+                    break;
+                case 'id_name':
+                    switch ($token[0]){
+                        case T_NAME_QUALIFIED:
+                        case T_NS_SEPARATOR:
+                        case T_STRING:
+                        case T_WHITESPACE:
+                        case T_COMMENT:
+                        case T_DOC_COMMENT:
+                            $id_name .= $token[1];
+                            break;
+                        case '(':
+                            if ($isShortClosure) {
+                                $open++;
+                            }
+                            if($context === 'new' || false !== strpos($id_name, '\\')){
+                                if($id_start_ci === 'self' || $id_start_ci === 'static') {
+                                    if (!$inside_structure) {
+                                        $isUsingScope = true;
+                                    }
+                                } elseif ($id_start !== '\\' && !in_array($id_start_ci, $class_keywords)) {
+                                    if ($classes === null) {
+                                        $classes = $this->getClasses();
+                                    }
+                                    if (isset($classes[$id_start_ci])) {
+                                        $id_start = $classes[$id_start_ci];
+                                    }
+                                    if($id_start[0] !== '\\'){
+                                        $id_start = $nsf . '\\' . $id_start;
+                                    }
+                                }
+                            } else {
+                                if($id_start !== '\\'){
+                                    if($functions === null){
+                                        $functions = $this->getFunctions();
+                                    }
+                                    if(isset($functions[$id_start_ci])){
+                                        $id_start = $functions[$id_start_ci];
+                                    } elseif ($nsf !== '\\' && function_exists($nsf . '\\' . $id_start)) {
+                                        $id_start = $nsf . '\\' . $id_start;
+                                        // Cache it to functions array
+                                        $functions[$id_start_ci] = $id_start;
+                                    }
+                                }
+                            }
+                            $code .= $id_start . $id_name . '(';
+                            $state = $lastState;
+                            break;
+                        case T_VARIABLE:
+                        case T_DOUBLE_COLON:
+                            if($id_start !== '\\') {
+                                if($id_start_ci === 'self' || $id_start_ci === 'parent'){
+                                    if (!$inside_structure) {
+                                        $isUsingScope = true;
+                                    }
+                                } elseif ($id_start_ci === 'static') {
+                                    if (!$inside_structure) {
+                                        $isUsingScope = $token[0] === T_DOUBLE_COLON;
+                                    }
+                                } elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))){
+                                    if ($classes === null) {
+                                        $classes = $this->getClasses();
+                                    }
+                                    if (isset($classes[$id_start_ci])) {
+                                        $id_start = $classes[$id_start_ci];
+                                    }
+                                    if($id_start[0] !== '\\'){
+                                        $id_start = $nsf . '\\' . $id_start;
+                                    }
+                                }
+                            }
+
+                            $code .= $id_start . $id_name . $token[1];
+                            $state = $token[0] === T_DOUBLE_COLON ? 'ignore_next' : $lastState;
+                            break;
+                        default:
+                            if($id_start !== '\\' && !defined($id_start)){
+                                if($constants === null){
+                                    $constants = $this->getConstants();
+                                }
+                                if(isset($constants[$id_start])){
+                                    $id_start = $constants[$id_start];
+                                } elseif($context === 'new'){
+                                    if(in_array($id_start_ci, $class_keywords)) {
+                                        if (!$inside_structure) {
+                                            $isUsingScope = true;
+                                        }
+                                    } else {
+                                        if ($classes === null) {
+                                            $classes = $this->getClasses();
+                                        }
+                                        if (isset($classes[$id_start_ci])) {
+                                            $id_start = $classes[$id_start_ci];
+                                        }
+                                        if ($id_start[0] !== '\\') {
+                                            $id_start = $nsf . '\\' . $id_start;
+                                        }
+                                    }
+                                } elseif($context === 'use' ||
+                                    $context === 'instanceof' ||
+                                    $context === 'args' ||
+                                    $context === 'return_type' ||
+                                    $context === 'extends' ||
+                                    $context === 'root'
+                                ){
+                                    if(in_array($id_start_ci, $class_keywords)){
+                                        if (!$inside_structure && !$id_start_ci === 'static') {
+                                            $isUsingScope = true;
+                                        }
+                                    } elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))){
+                                        if($classes === null){
+                                            $classes = $this->getClasses();
+                                        }
+                                        if(isset($classes[$id_start_ci])){
+                                            $id_start = $classes[$id_start_ci];
+                                        }
+                                        if($id_start[0] !== '\\'){
+                                            $id_start = $nsf . '\\' . $id_start;
+                                        }
+                                    }
+                                }
+                            }
+                            $code .= $id_start . $id_name;
+                            $state = $lastState;
+                            $i--;//reprocess last token
+                    }
+                    break;
+                case 'anonymous':
+                    switch ($token[0]) {
+                        case T_NS_SEPARATOR:
+                        case T_STRING:
+                            $id_start = $token[1];
+                            $id_start_ci = strtolower($id_start);
+                            $id_name = '';
+                            $state = 'id_name';
+                            $context = 'extends';
+                            $lastState = 'anonymous';
+                        break;
+                        case '{':
+                            $state = 'closure';
+                            if (!$inside_structure) {
+                                $inside_structure = true;
+                                $inside_structure_mark = $open;
+                            }
+                            $i--;
+                            break;
+                        default:
+                            $code .= is_array($token) ? $token[1] : $token;
+                    }
+                    break;
+            }
+        }
+
+        if ($isShortClosure) {
+            $this->useVariables = $this->getStaticVariables();
+        } else {
+            $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
+        }
+
+        $this->isShortClosure = $isShortClosure;
+        $this->isBindingRequired = $isUsingThisObject;
+        $this->isScopeRequired = $isUsingScope;
+        $this->code = $code;
+
+        return $this->code;
+    }
+
+    /**
+     * @return array
+     */
+    private static function getBuiltinTypes()
+    {
+        // PHP 5
+        if (\PHP_MAJOR_VERSION === 5) {
+            return ['array', 'callable'];
+        }
+
+        // PHP 8
+        if (\PHP_MAJOR_VERSION === 8) {
+            return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null'];
+        }
+
+        // PHP 7
+        switch (\PHP_MINOR_VERSION) {
+            case 0:
+                return ['array', 'callable', 'string', 'int', 'bool', 'float'];
+            case 1:
+                return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void'];
+            default:
+                return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object'];
+        }
+    }
+
+    /**
+     * @return array
+     */
+    public function getUseVariables()
+    {
+        if($this->useVariables !== null){
+            return $this->useVariables;
+        }
+
+        $tokens = $this->getTokens();
+        $use = array();
+        $state = 'start';
+
+        foreach ($tokens as &$token) {
+            $is_array = is_array($token);
+
+            switch ($state) {
+                case 'start':
+                    if ($is_array && $token[0] === T_USE) {
+                        $state = 'use';
+                    }
+                    break;
+                case 'use':
+                    if ($is_array) {
+                        if ($token[0] === T_VARIABLE) {
+                            $use[] = substr($token[1], 1);
+                        }
+                    } elseif ($token == ')') {
+                        break 2;
+                    }
+                    break;
+            }
+        }
+
+        $this->useVariables = empty($use) ? $use : array_intersect_key($this->getStaticVariables(), array_flip($use));
+
+        return $this->useVariables;
+    }
+
+    /**
+     * return bool
+     */
+    public function isBindingRequired()
+    {
+        if($this->isBindingRequired === null){
+            $this->getCode();
+        }
+
+        return $this->isBindingRequired;
+    }
+
+    /**
+     * return bool
+     */
+    public function isScopeRequired()
+    {
+        if($this->isScopeRequired === null){
+            $this->getCode();
+        }
+
+        return $this->isScopeRequired;
+    }
+
+    /**
+     * @return string
+     */
+    protected function getHashedFileName()
+    {
+        if ($this->hashedName === null) {
+            $this->hashedName = sha1($this->getFileName());
+        }
+
+        return $this->hashedName;
+    }
+
+    /**
+     * @return array
+     */
+    protected function getFileTokens()
+    {
+        $key = $this->getHashedFileName();
+
+        if (!isset(static::$files[$key])) {
+            static::$files[$key] = token_get_all(file_get_contents($this->getFileName()));
+        }
+
+        return static::$files[$key];
+    }
+
+    /**
+     * @return array
+     */
+    protected function getTokens()
+    {
+        if ($this->tokens === null) {
+            $tokens = $this->getFileTokens();
+            $startLine = $this->getStartLine();
+            $endLine = $this->getEndLine();
+            $results = array();
+            $start = false;
+
+            foreach ($tokens as &$token) {
+                if (!is_array($token)) {
+                    if ($start) {
+                        $results[] = $token;
+                    }
+
+                    continue;
+                }
+
+                $line = $token[2];
+
+                if ($line <= $endLine) {
+                    if ($line >= $startLine) {
+                        $start = true;
+                        $results[] = $token;
+                    }
+
+                    continue;
+                }
+
+                break;
+            }
+
+            $this->tokens = $results;
+        }
+
+        return $this->tokens;
+    }
+
+    /**
+     * @return array
+     */
+    protected function getClasses()
+    {
+        $key = $this->getHashedFileName();
+
+        if (!isset(static::$classes[$key])) {
+            $this->fetchItems();
+        }
+
+        return static::$classes[$key];
+    }
+
+    /**
+     * @return array
+     */
+    protected function getFunctions()
+    {
+        $key = $this->getHashedFileName();
+
+        if (!isset(static::$functions[$key])) {
+            $this->fetchItems();
+        }
+
+        return static::$functions[$key];
+    }
+
+    /**
+     * @return array
+     */
+    protected function getConstants()
+    {
+        $key = $this->getHashedFileName();
+
+        if (!isset(static::$constants[$key])) {
+            $this->fetchItems();
+        }
+
+        return static::$constants[$key];
+    }
+
+    /**
+     * @return array
+     */
+    protected function getStructures()
+    {
+        $key = $this->getHashedFileName();
+
+        if (!isset(static::$structures[$key])) {
+            $this->fetchItems();
+        }
+
+        return static::$structures[$key];
+    }
+
+    protected function fetchItems()
+    {
+        $key = $this->getHashedFileName();
+
+        $classes = array();
+        $functions = array();
+        $constants = array();
+        $structures = array();
+        $tokens = $this->getFileTokens();
+
+        $open = 0;
+        $state = 'start';
+        $lastState = '';
+        $prefix = '';
+        $name = '';
+        $alias = '';
+        $isFunc = $isConst = false;
+
+        $startLine = $endLine = 0;
+        $structType = $structName = '';
+        $structIgnore = false;
+
+        foreach ($tokens as $token) {
+
+            switch ($state) {
+                case 'start':
+                    switch ($token[0]) {
+                        case T_CLASS:
+                        case T_INTERFACE:
+                        case T_TRAIT:
+                            $state = 'before_structure';
+                            $startLine = $token[2];
+                            $structType = $token[0] == T_CLASS
+                                                    ? 'class'
+                                                    : ($token[0] == T_INTERFACE ? 'interface' : 'trait');
+                            break;
+                        case T_USE:
+                            $state = 'use';
+                            $prefix = $name = $alias = '';
+                            $isFunc = $isConst = false;
+                            break;
+                        case T_FUNCTION:
+                            $state = 'structure';
+                            $structIgnore = true;
+                            break;
+                        case T_NEW:
+                            $state = 'new';
+                            break;
+                        case T_OBJECT_OPERATOR:
+                        case T_DOUBLE_COLON:
+                            $state = 'invoke';
+                            break;
+                    }
+                    break;
+                case 'use':
+                    switch ($token[0]) {
+                        case T_FUNCTION:
+                            $isFunc = true;
+                            break;
+                        case T_CONST:
+                            $isConst = true;
+                            break;
+                        case T_NS_SEPARATOR:
+                            $name .= $token[1];
+                            break;
+                        case T_STRING:
+                            $name .= $token[1];
+                            $alias = $token[1];
+                            break;
+                        case T_NAME_QUALIFIED:
+                            $name .= $token[1];
+                            $pieces = explode('\\', $token[1]);
+                            $alias = end($pieces);
+                            break;
+                        case T_AS:
+                            $lastState = 'use';
+                            $state = 'alias';
+                            break;
+                        case '{':
+                            $prefix = $name;
+                            $name = $alias = '';
+                            $state = 'use-group';
+                            break;
+                        case ',':
+                        case ';':
+                            if ($name === '' || $name[0] !== '\\') {
+                                $name = '\\' . $name;
+                            }
+
+                            if ($alias !== '') {
+                                if ($isFunc) {
+                                    $functions[strtolower($alias)] = $name;
+                                } elseif ($isConst) {
+                                    $constants[$alias] = $name;
+                                } else {
+                                    $classes[strtolower($alias)] = $name;
+                                }
+                            }
+                            $name = $alias = '';
+                            $state = $token === ';' ? 'start' : 'use';
+                            break;
+                    }
+                    break;
+                case 'use-group':
+                    switch ($token[0]) {
+                        case T_NS_SEPARATOR:
+                            $name .= $token[1];
+                            break;
+                        case T_NAME_QUALIFIED:
+                            $name .= $token[1];
+                            $pieces = explode('\\', $token[1]);
+                            $alias = end($pieces);
+                            break;
+                        case T_STRING:
+                            $name .= $token[1];
+                            $alias = $token[1];
+                            break;
+                        case T_AS:
+                            $lastState = 'use-group';
+                            $state = 'alias';
+                            break;
+                        case ',':
+                        case '}':
+
+                            if ($prefix === '' || $prefix[0] !== '\\') {
+                                $prefix = '\\' . $prefix;
+                            }
+
+                            if ($alias !== '') {
+                                if ($isFunc) {
+                                    $functions[strtolower($alias)] = $prefix . $name;
+                                } elseif ($isConst) {
+                                    $constants[$alias] = $prefix . $name;
+                                } else {
+                                    $classes[strtolower($alias)] = $prefix . $name;
+                                }
+                            }
+                            $name = $alias = '';
+                            $state = $token === '}' ? 'use' : 'use-group';
+                            break;
+                    }
+                    break;
+                case 'alias':
+                    if ($token[0] === T_STRING) {
+                        $alias = $token[1];
+                        $state = $lastState;
+                    }
+                    break;
+                case 'new':
+                    switch ($token[0]) {
+                        case T_WHITESPACE:
+                        case T_COMMENT:
+                        case T_DOC_COMMENT:
+                            break 2;
+                        case T_CLASS:
+                            $state = 'structure';
+                            $structIgnore = true;
+                            break;
+                        default:
+                            $state = 'start';
+                    }
+                    break;
+                case 'invoke':
+                    switch ($token[0]) {
+                        case T_WHITESPACE:
+                        case T_COMMENT:
+                        case T_DOC_COMMENT:
+                            break 2;
+                        default:
+                            $state = 'start';
+                    }
+                    break;
+                case 'before_structure':
+                    if ($token[0] == T_STRING) {
+                        $structName = $token[1];
+                        $state = 'structure';
+                    }
+                    break;
+                case 'structure':
+                    switch ($token[0]) {
+                        case '{':
+                        case T_CURLY_OPEN:
+                        case T_DOLLAR_OPEN_CURLY_BRACES:
+                            $open++;
+                            break;
+                        case '}':
+                            if (--$open == 0) {
+                                if(!$structIgnore){
+                                    $structures[] = array(
+                                        'type' => $structType,
+                                        'name' => $structName,
+                                        'start' => $startLine,
+                                        'end' => $endLine,
+                                    );
+                                }
+                                $structIgnore = false;
+                                $state = 'start';
+                            }
+                            break;
+                        default:
+                            if (is_array($token)) {
+                                $endLine = $token[2];
+                            }
+                    }
+                    break;
+            }
+        }
+
+        static::$classes[$key] = $classes;
+        static::$functions[$key] = $functions;
+        static::$constants[$key] = $constants;
+        static::$structures[$key] = $structures;
+    }
+
+    private function parseNameQualified($token)
+    {
+        $pieces = explode('\\', $token);
+
+        $id_start = array_shift($pieces);
+
+        $id_start_ci = strtolower($id_start);
+
+        $id_name = '\\' . implode('\\', $pieces);
+
+        return [$id_start, $id_start_ci, $id_name];
+    }
+}

+ 18 - 0
api/vendor/opis/closure/src/SecurityException.php

@@ -0,0 +1,18 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+use Exception;
+
+/**
+ * Security exception class
+ */
+class SecurityException extends Exception
+{
+
+}

+ 42 - 0
api/vendor/opis/closure/src/SecurityProvider.php

@@ -0,0 +1,42 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+class SecurityProvider implements ISecurityProvider
+{
+    /** @var  string */
+    protected $secret;
+
+    /**
+     * SecurityProvider constructor.
+     * @param string $secret
+     */
+    public function __construct($secret)
+    {
+        $this->secret = $secret;
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function sign($closure)
+    {
+        return array(
+            'closure' => $closure,
+            'hash' => base64_encode(hash_hmac('sha256', $closure, $this->secret, true)),
+        );
+    }
+
+    /**
+     * @inheritdoc
+     */
+    public function verify(array $data)
+    {
+        return base64_encode(hash_hmac('sha256', $data['closure'], $this->secret, true)) === $data['hash'];
+    }
+}

+ 31 - 0
api/vendor/opis/closure/src/SelfReference.php

@@ -0,0 +1,31 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+
+/**
+ * Helper class used to indicate a reference to an object
+ * @internal
+ */
+class SelfReference
+{
+    /**
+     * @var string An unique hash representing the object
+     */
+    public $hash;
+
+    /**
+     * Constructor
+     *
+     * @param string $hash
+     */
+    public function __construct($hash)
+    {
+        $this->hash = $hash;
+    }
+}

+ 678 - 0
api/vendor/opis/closure/src/SerializableClosure.php

@@ -0,0 +1,678 @@
+<?php
+/* ===========================================================================
+ * Copyright (c) 2018-2019 Zindex Software
+ *
+ * Licensed under the MIT License
+ * =========================================================================== */
+
+namespace Opis\Closure;
+
+use Closure;
+use Serializable;
+use SplObjectStorage;
+use ReflectionObject;
+
+/**
+ * Provides a wrapper for serialization of closures
+ */
+class SerializableClosure implements Serializable
+{
+    /**
+     * @var Closure Wrapped closure
+     *
+     * @see \Opis\Closure\SerializableClosure::getClosure()
+     */
+    protected $closure;
+
+    /**
+     * @var ReflectionClosure A reflection instance for closure
+     *
+     * @see \Opis\Closure\SerializableClosure::getReflector()
+     */
+    protected $reflector;
+
+    /**
+     * @var mixed Used at deserialization to hold variables
+     *
+     * @see \Opis\Closure\SerializableClosure::unserialize()
+     * @see \Opis\Closure\SerializableClosure::getReflector()
+     */
+    protected $code;
+
+    /**
+     * @var string Closure's ID
+     */
+    protected $reference;
+
+    /**
+     * @var string Closure scope
+     */
+    protected $scope;
+
+    /**
+     * @var ClosureContext Context of closure, used in serialization
+     */
+    protected static $context;
+
+    /**
+     * @var ISecurityProvider|null
+     */
+    protected static $securityProvider;
+
+    /** Array recursive constant*/
+    const ARRAY_RECURSIVE_KEY = '¯\_(ツ)_/¯';
+
+    /**
+     * Constructor
+     *
+     * @param   Closure $closure Closure you want to serialize
+     */
+    public function __construct(Closure $closure)
+    {
+        $this->closure = $closure;
+        if (static::$context !== null) {
+            $this->scope = static::$context->scope;
+            $this->scope->toserialize++;
+        }
+    }
+
+    /**
+     * Get the Closure object
+     *
+     * @return  Closure The wrapped closure
+     */
+    public function getClosure()
+    {
+        return $this->closure;
+    }
+
+    /**
+     * Get the reflector for closure
+     *
+     * @return  ReflectionClosure
+     */
+    public function getReflector()
+    {
+        if ($this->reflector === null) {
+            $this->reflector = new ReflectionClosure($this->closure);
+            $this->code = null;
+        }
+
+        return $this->reflector;
+    }
+
+    /**
+     * Implementation of magic method __invoke()
+     */
+    public function __invoke()
+    {
+        return call_user_func_array($this->closure, func_get_args());
+    }
+
+    /**
+     * Implementation of Serializable::serialize()
+     *
+     * @return  string  The serialized closure
+     */
+    public function serialize()
+    {
+        if ($this->scope === null) {
+            $this->scope = new ClosureScope();
+            $this->scope->toserialize++;
+        }
+
+        $this->scope->serializations++;
+
+        $scope = $object = null;
+        $reflector = $this->getReflector();
+
+        if($reflector->isBindingRequired()){
+            $object = $reflector->getClosureThis();
+            static::wrapClosures($object, $this->scope);
+            if($scope = $reflector->getClosureScopeClass()){
+                $scope = $scope->name;
+            }
+        } else {
+            if($scope = $reflector->getClosureScopeClass()){
+                $scope = $scope->name;
+            }
+        }
+
+        $this->reference = spl_object_hash($this->closure);
+
+        $this->scope[$this->closure] = $this;
+
+        $use = $this->transformUseVariables($reflector->getUseVariables());
+        $code = $reflector->getCode();
+
+        $this->mapByReference($use);
+
+        $ret = \serialize(array(
+            'use' => $use,
+            'function' => $code,
+            'scope' => $scope,
+            'this' => $object,
+            'self' => $this->reference,
+        ));
+
+        if (static::$securityProvider !== null) {
+            $data = static::$securityProvider->sign($ret);
+            $ret =  '@' . $data['hash'] . '.' . $data['closure'];
+        }
+
+        if (!--$this->scope->serializations && !--$this->scope->toserialize) {
+            $this->scope = null;
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Transform the use variables before serialization.
+     *
+     * @param  array  $data The Closure's use variables
+     * @return array
+     */
+    protected function transformUseVariables($data)
+    {
+        return $data;
+    }
+
+    /**
+     * Implementation of Serializable::unserialize()
+     *
+     * @param   string $data Serialized data
+     * @throws SecurityException
+     */
+    public function unserialize($data)
+    {
+        ClosureStream::register();
+
+        if (static::$securityProvider !== null) {
+            if ($data[0] !== '@') {
+                throw new SecurityException("The serialized closure is not signed. ".
+                    "Make sure you use a security provider for both serialization and unserialization.");
+            }
+
+            if ($data[1] !== '{') {
+                $separator = strpos($data, '.');
+                if ($separator === false) {
+                    throw new SecurityException('Invalid signed closure');
+                }
+                $hash = substr($data, 1, $separator - 1);
+                $closure = substr($data, $separator + 1);
+
+                $data = ['hash' => $hash, 'closure' => $closure];
+
+                unset($hash, $closure);
+            } else {
+                $data = json_decode(substr($data, 1), true);
+            }
+
+            if (!is_array($data) || !static::$securityProvider->verify($data)) {
+                throw new SecurityException("Your serialized closure might have been modified and it's unsafe to be unserialized. " .
+                    "Make sure you use the same security provider, with the same settings, " .
+                    "both for serialization and unserialization.");
+            }
+
+            $data = $data['closure'];
+        } elseif ($data[0] === '@') {
+            if ($data[1] !== '{') {
+                $separator = strpos($data, '.');
+                if ($separator === false) {
+                    throw new SecurityException('Invalid signed closure');
+                }
+                $hash = substr($data, 1, $separator - 1);
+                $closure = substr($data, $separator + 1);
+
+                $data = ['hash' => $hash, 'closure' => $closure];
+
+                unset($hash, $closure);
+            } else {
+                $data = json_decode(substr($data, 1), true);
+            }
+
+            if (!is_array($data) || !isset($data['closure']) || !isset($data['hash'])) {
+                throw new SecurityException('Invalid signed closure');
+            }
+
+            $data = $data['closure'];
+        }
+
+        $this->code = \unserialize($data);
+
+        // unset data
+        unset($data);
+
+        $this->code['objects'] = array();
+
+        if ($this->code['use']) {
+            $this->scope = new ClosureScope();
+            $this->code['use'] = $this->resolveUseVariables($this->code['use']);
+            $this->mapPointers($this->code['use']);
+            extract($this->code['use'], EXTR_OVERWRITE | EXTR_REFS);
+            $this->scope = null;
+        }
+
+        $this->closure = include(ClosureStream::STREAM_PROTO . '://' . $this->code['function']);
+
+        if($this->code['this'] === $this){
+            $this->code['this'] = null;
+        }
+
+        $this->closure = $this->closure->bindTo($this->code['this'], $this->code['scope']);
+
+        if(!empty($this->code['objects'])){
+            foreach ($this->code['objects'] as $item){
+                $item['property']->setValue($item['instance'], $item['object']->getClosure());
+            }
+        }
+
+        $this->code = $this->code['function'];
+    }
+
+    /**
+     * Resolve the use variables after unserialization.
+     *
+     * @param  array  $data The Closure's transformed use variables
+     * @return array
+     */
+    protected function resolveUseVariables($data)
+    {
+        return $data;
+    }
+
+    /**
+     * Wraps a closure and sets the serialization context (if any)
+     *
+     * @param   Closure $closure Closure to be wrapped
+     *
+     * @return  self    The wrapped closure
+     */
+    public static function from(Closure $closure)
+    {
+        if (static::$context === null) {
+            $instance = new static($closure);
+        } elseif (isset(static::$context->scope[$closure])) {
+            $instance = static::$context->scope[$closure];
+        } else {
+            $instance = new static($closure);
+            static::$context->scope[$closure] = $instance;
+        }
+
+        return $instance;
+    }
+
+    /**
+     * Increments the context lock counter or creates a new context if none exist
+     */
+    public static function enterContext()
+    {
+        if (static::$context === null) {
+            static::$context = new ClosureContext();
+        }
+
+        static::$context->locks++;
+    }
+
+    /**
+     * Decrements the context lock counter and destroy the context when it reaches to 0
+     */
+    public static function exitContext()
+    {
+        if (static::$context !== null && !--static::$context->locks) {
+            static::$context = null;
+        }
+    }
+
+    /**
+     * @param string $secret
+     */
+    public static function setSecretKey($secret)
+    {
+        if(static::$securityProvider === null){
+            static::$securityProvider = new SecurityProvider($secret);
+        }
+    }
+
+    /**
+     * @param ISecurityProvider $securityProvider
+     */
+    public static function addSecurityProvider(ISecurityProvider $securityProvider)
+    {
+        static::$securityProvider = $securityProvider;
+    }
+
+    /**
+     * Remove security provider
+     */
+    public static function removeSecurityProvider()
+    {
+        static::$securityProvider = null;
+    }
+
+    /**
+     * @return null|ISecurityProvider
+     */
+    public static function getSecurityProvider()
+    {
+        return static::$securityProvider;
+    }
+
+    /**
+     * Wrap closures
+     *
+     * @internal
+     * @param $data
+     * @param ClosureScope|SplObjectStorage|null $storage
+     */
+    public static function wrapClosures(&$data, SplObjectStorage $storage = null)
+    {
+        if($storage === null){
+            $storage = static::$context->scope;
+        }
+
+        if($data instanceof Closure){
+            $data = static::from($data);
+        } elseif (is_array($data)){
+            if(isset($data[self::ARRAY_RECURSIVE_KEY])){
+                return;
+            }
+            $data[self::ARRAY_RECURSIVE_KEY] = true;
+            foreach ($data as $key => &$value){
+                if($key === self::ARRAY_RECURSIVE_KEY){
+                    continue;
+                }
+                static::wrapClosures($value, $storage);
+            }
+            unset($value);
+            unset($data[self::ARRAY_RECURSIVE_KEY]);
+        } elseif($data instanceof \stdClass){
+            if(isset($storage[$data])){
+                $data = $storage[$data];
+                return;
+            }
+            $data = $storage[$data] = clone($data);
+            foreach ($data as &$value){
+                static::wrapClosures($value, $storage);
+            }
+            unset($value);
+        } elseif (is_object($data) && ! $data instanceof static){
+            if(isset($storage[$data])){
+                $data = $storage[$data];
+                return;
+            }
+            $instance = $data;
+            $reflection = new ReflectionObject($instance);
+            if(!$reflection->isUserDefined()){
+                $storage[$instance] = $data;
+                return;
+            }
+            $storage[$instance] = $data = $reflection->newInstanceWithoutConstructor();
+
+            do{
+                if(!$reflection->isUserDefined()){
+                    break;
+                }
+                foreach ($reflection->getProperties() as $property){
+                    if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
+                        continue;
+                    }
+                    $property->setAccessible(true);
+                    if (PHP_VERSION >= 7.4 && !$property->isInitialized($instance)) {
+                        continue;
+                    }
+                    $value = $property->getValue($instance);
+                    if(is_array($value) || is_object($value)){
+                        static::wrapClosures($value, $storage);
+                    }
+                    $property->setValue($data, $value);
+                };
+            } while($reflection = $reflection->getParentClass());
+        }
+    }
+
+    /**
+     * Unwrap closures
+     *
+     * @internal
+     * @param $data
+     * @param SplObjectStorage|null $storage
+     */
+    public static function unwrapClosures(&$data, SplObjectStorage $storage = null)
+    {
+        if($storage === null){
+            $storage = static::$context->scope;
+        }
+
+        if($data instanceof static){
+            $data = $data->getClosure();
+        } elseif (is_array($data)){
+            if(isset($data[self::ARRAY_RECURSIVE_KEY])){
+                return;
+            }
+            $data[self::ARRAY_RECURSIVE_KEY] = true;
+            foreach ($data as $key => &$value){
+                if($key === self::ARRAY_RECURSIVE_KEY){
+                    continue;
+                }
+                static::unwrapClosures($value, $storage);
+            }
+            unset($data[self::ARRAY_RECURSIVE_KEY]);
+        }elseif ($data instanceof \stdClass){
+            if(isset($storage[$data])){
+                return;
+            }
+            $storage[$data] = true;
+            foreach ($data as &$property){
+                static::unwrapClosures($property, $storage);
+            }
+        } elseif (is_object($data) && !($data instanceof Closure)){
+            if(isset($storage[$data])){
+                return;
+            }
+            $storage[$data] = true;
+            $reflection = new ReflectionObject($data);
+
+            do{
+                if(!$reflection->isUserDefined()){
+                    break;
+                }
+                foreach ($reflection->getProperties() as $property){
+                    if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
+                        continue;
+                    }
+                    $property->setAccessible(true);
+                    if (PHP_VERSION >= 7.4 && !$property->isInitialized($data)) {
+                        continue;
+                    }
+                    $value = $property->getValue($data);
+                    if(is_array($value) || is_object($value)){
+                        static::unwrapClosures($value, $storage);
+                        $property->setValue($data, $value);
+                    }
+                };
+            } while($reflection = $reflection->getParentClass());
+        }
+    }
+
+    /**
+     * Creates a new closure from arbitrary code,
+     * emulating create_function, but without using eval
+     *
+     * @param string$args
+     * @param string $code
+     * @return Closure
+     */
+    public static function createClosure($args, $code)
+    {
+        ClosureStream::register();
+        return include(ClosureStream::STREAM_PROTO . '://function(' . $args. '){' . $code . '};');
+    }
+
+    /**
+     * Internal method used to map closure pointers
+     * @internal
+     * @param $data
+     */
+    protected function mapPointers(&$data)
+    {
+        $scope = $this->scope;
+
+        if ($data instanceof static) {
+            $data = &$data->closure;
+        } elseif (is_array($data)) {
+            if(isset($data[self::ARRAY_RECURSIVE_KEY])){
+                return;
+            }
+            $data[self::ARRAY_RECURSIVE_KEY] = true;
+            foreach ($data as $key => &$value){
+                if($key === self::ARRAY_RECURSIVE_KEY){
+                    continue;
+                } elseif ($value instanceof static) {
+                    $data[$key] = &$value->closure;
+                } elseif ($value instanceof SelfReference && $value->hash === $this->code['self']){
+                    $data[$key] = &$this->closure;
+                } else {
+                    $this->mapPointers($value);
+                }
+            }
+            unset($value);
+            unset($data[self::ARRAY_RECURSIVE_KEY]);
+        } elseif ($data instanceof \stdClass) {
+            if(isset($scope[$data])){
+                return;
+            }
+            $scope[$data] = true;
+            foreach ($data as $key => &$value){
+                if ($value instanceof SelfReference && $value->hash === $this->code['self']){
+                    $data->{$key} = &$this->closure;
+                } elseif(is_array($value) || is_object($value)) {
+                    $this->mapPointers($value);
+                }
+            }
+            unset($value);
+        } elseif (is_object($data) && !($data instanceof Closure)){
+            if(isset($scope[$data])){
+                return;
+            }
+            $scope[$data] = true;
+            $reflection = new ReflectionObject($data);
+            do{
+                if(!$reflection->isUserDefined()){
+                    break;
+                }
+                foreach ($reflection->getProperties() as $property){
+                    if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
+                        continue;
+                    }
+                    $property->setAccessible(true);
+                    if (PHP_VERSION >= 7.4 && !$property->isInitialized($data)) {
+                        continue;
+                    }
+                    $item = $property->getValue($data);
+                    if ($item instanceof SerializableClosure || ($item instanceof SelfReference && $item->hash === $this->code['self'])) {
+                        $this->code['objects'][] = array(
+                            'instance' => $data,
+                            'property' => $property,
+                            'object' => $item instanceof SelfReference ? $this : $item,
+                        );
+                    } elseif (is_array($item) || is_object($item)) {
+                        $this->mapPointers($item);
+                        $property->setValue($data, $item);
+                    }
+                }
+            } while($reflection = $reflection->getParentClass());
+        }
+    }
+
+    /**
+     * Internal method used to map closures by reference
+     *
+     * @internal
+     * @param   mixed &$data
+     */
+    protected function mapByReference(&$data)
+    {
+        if ($data instanceof Closure) {
+            if($data === $this->closure){
+                $data = new SelfReference($this->reference);
+                return;
+            }
+
+            if (isset($this->scope[$data])) {
+                $data = $this->scope[$data];
+                return;
+            }
+
+            $instance = new static($data);
+
+            if (static::$context !== null) {
+                static::$context->scope->toserialize--;
+            } else {
+                $instance->scope = $this->scope;
+            }
+
+            $data = $this->scope[$data] = $instance;
+        } elseif (is_array($data)) {
+            if(isset($data[self::ARRAY_RECURSIVE_KEY])){
+                return;
+            }
+            $data[self::ARRAY_RECURSIVE_KEY] = true;
+            foreach ($data as $key => &$value){
+                if($key === self::ARRAY_RECURSIVE_KEY){
+                    continue;
+                }
+                $this->mapByReference($value);
+            }
+            unset($value);
+            unset($data[self::ARRAY_RECURSIVE_KEY]);
+        } elseif ($data instanceof \stdClass) {
+            if(isset($this->scope[$data])){
+                $data = $this->scope[$data];
+                return;
+            }
+            $instance = $data;
+            $this->scope[$instance] = $data = clone($data);
+
+            foreach ($data as &$value){
+                $this->mapByReference($value);
+            }
+            unset($value);
+        } elseif (is_object($data) && !$data instanceof SerializableClosure){
+            if(isset($this->scope[$data])){
+                $data = $this->scope[$data];
+                return;
+            }
+
+            $instance = $data;
+            $reflection = new ReflectionObject($data);
+            if(!$reflection->isUserDefined()){
+                $this->scope[$instance] = $data;
+                return;
+            }
+            $this->scope[$instance] = $data = $reflection->newInstanceWithoutConstructor();
+
+            do{
+                if(!$reflection->isUserDefined()){
+                    break;
+                }
+                foreach ($reflection->getProperties() as $property){
+                    if($property->isStatic() || !$property->getDeclaringClass()->isUserDefined()){
+                        continue;
+                    }
+                    $property->setAccessible(true);
+                    if (PHP_VERSION >= 7.4 && !$property->isInitialized($instance)) {
+                        continue;
+                    }
+                    $value = $property->getValue($instance);
+                    if(is_array($value) || is_object($value)){
+                        $this->mapByReference($value);
+                    }
+                    $property->setValue($data, $value);
+                }
+            } while($reflection = $reflection->getParentClass());
+        }
+    }
+
+}

+ 55 - 0
api/vendor/spatie/async/.github/CONTRIBUTING.md

@@ -0,0 +1,55 @@
+# Contributing
+
+Contributions are **welcome** and will be fully **credited**.
+
+Please read and understand the contribution guide before creating an issue or pull request.
+
+## Etiquette
+
+This project is open source, and as such, the maintainers give their free time to build and maintain the source code
+held within. They make the code freely available in the hope that it will be of use to other developers. It would be
+extremely unfair for them to suffer abuse or anger for their hard work.
+
+Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
+world that developers are civilized and selfless people.
+
+It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
+quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
+
+## Viability
+
+When requesting or submitting new features, first consider whether it might be useful to others. Open
+source projects are used by many developers, who may have entirely different needs to your own. Think about
+whether or not your feature is likely to be used by other users of the project.
+
+## Procedure
+
+Before filing an issue:
+
+- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
+- Check to make sure your feature suggestion isn't already present within the project.
+- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
+- Check the pull requests tab to ensure that the feature isn't already in progress.
+
+Before submitting a pull request:
+
+- Check the codebase to ensure that your feature doesn't already exist.
+- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
+
+## Requirements
+
+If the project maintainer has any additional requirements, you will find them listed here.
+
+- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).
+
+- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
+
+- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
+
+- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
+
+- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
+
+- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
+
+**Happy coding**!

+ 1 - 0
api/vendor/spatie/async/.github/FUNDING.yml

@@ -0,0 +1 @@
+github: spatie

+ 3 - 0
api/vendor/spatie/async/.github/SECURITY.md

@@ -0,0 +1,3 @@
+# Security Policy
+
+If you discover any security related issues, please email freek@spatie.be instead of using the issue tracker.

+ 23 - 0
api/vendor/spatie/async/.github/workflows/php-cs-fixer.yml

@@ -0,0 +1,23 @@
+name: Check & fix styling
+
+on: [push]
+
+jobs:
+  php-cs-fixer:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+        with:
+          ref: ${{ github.head_ref }}
+
+      - name: Run PHP CS Fixer
+        uses: docker://oskarstark/php-cs-fixer-ga
+        with:
+          args: --config=.php_cs.dist --allow-risky=yes
+
+      - name: Commit changes
+        uses: stefanzweifel/git-auto-commit-action@v4
+        with:
+          commit_message: Fix styling

+ 39 - 0
api/vendor/spatie/async/.github/workflows/run-tests.yml

@@ -0,0 +1,39 @@
+name: Tests
+
+on: [push, pull_request]
+
+jobs:
+  test:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: true
+      matrix:
+        os: [ubuntu-latest, windows-latest]
+        php: [7.1, 7.2, 7.3, 7.4, 8.0]
+        dependency-version: [prefer-lowest, prefer-stable]
+
+    name: P${{ matrix.php }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+
+      - name: Cache dependencies
+        uses: actions/cache@v2
+        with:
+          path: ~/.composer/cache/files
+          key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
+
+      - name: Setup PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: ${{ matrix.php }}
+          extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
+          coverage: none
+
+      - name: Install dependencies
+        run: |
+          composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --ignore-platform-reqs
+
+      - name: Execute tests
+        run: vendor/bin/phpunit

+ 43 - 0
api/vendor/spatie/async/.php_cs.dist

@@ -0,0 +1,43 @@
+<?php
+
+$finder = Symfony\Component\Finder\Finder::create()
+    ->notPath('bootstrap/*')
+    ->notPath('storage/*')
+    ->notPath('resources/view/mail/*')
+    ->in([
+        __DIR__ . '/src',
+        __DIR__ . '/tests',
+    ])
+    ->name('*.php')
+    ->notName('*.blade.php')
+    ->ignoreDotFiles(true)
+    ->ignoreVCS(true);
+
+return PhpCsFixer\Config::create()
+    ->setRules([
+        '@PSR2' => true,
+        'array_syntax' => ['syntax' => 'short'],
+        'ordered_imports' => ['sortAlgorithm' => 'alpha'],
+        'no_unused_imports' => true,
+        'not_operator_with_successor_space' => true,
+        'trailing_comma_in_multiline_array' => true,
+        'phpdoc_scalar' => true,
+        'unary_operator_spaces' => true,
+        'binary_operator_spaces' => true,
+        'blank_line_before_statement' => [
+            'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
+        ],
+        'phpdoc_single_line_var_spacing' => true,
+        'phpdoc_var_without_name' => true,
+        'class_attributes_separation' => [
+            'elements' => [
+                'method',
+            ],
+        ],
+        'method_argument_space' => [
+            'on_multiline' => 'ensure_fully_multiline',
+            'keep_multiple_spaces_after_comma' => true,
+        ],
+        'single_trait_insert_per_statement' => true,
+    ])
+    ->setFinder($finder);

+ 63 - 0
api/vendor/spatie/async/CHANGELOG.md

@@ -0,0 +1,63 @@
+# Changelog
+
+All notable changes to `async` will be documented in this file
+
+## 1.5.2 - 2020-11-20
+
+- Configure task in synchronous process
+- Add Pool::forceSynchronous function
+
+## 1.5.1 - 2020-11-20
+
+- Support for PHP 8
+
+## 1.5.0 - 2020-09-18
+
+- Add fallback to SerializableException to handle "complex" exceptions (#119)
+
+## 1.4.1 - 2020-08-19
+
+- Properly stop process on timeout (#105)
+
+## 1.4.0 - 2020-04-15
+
+- Make binary configurable (#111 and #112)
+
+## 1.3.0 - 2020-03-17
+
+- Support microsecond timeouts (#109)
+
+## 1.2.0 - 2020-02-14
+
+- Add ability to stop the pool early (#56)
+
+## 1.1.1 - 2019-12-24
+
+- allow Symfony 5 components
+
+## 1.1.0 - 2019-09-30
+
+- Make output length configurable (#86)
+
+## 1.0.4 - 2019-08-02
+
+- Fix for `SynchronousProcess::resolveErrorOutput` (#73)
+
+## 1.0.3 - 2019-07-22
+
+- Fix for Symfony Process argument deprecation
+
+## 1.0.1 - 2019-05-17
+
+- Synchronous execution time bugfix
+
+## 1.0.1 - 2019-05-07
+
+- Check on PCNTL support before registering listeners
+
+## 1.0.0 - 2019-03-22
+
+- First stable release
+- Add the ability to catch exceptions by type
+- Thrown errors can only have one handler. 
+See [UPGRADING](./UPGRADING.md#100) for more information.

+ 55 - 0
api/vendor/spatie/async/CONTRIBUTING.md

@@ -0,0 +1,55 @@
+# Contributing
+
+Contributions are **welcome** and will be fully **credited**.
+
+Please read and understand the contribution guide before creating an issue or pull request.
+
+## Etiquette
+
+This project is open source, and as such, the maintainers give their free time to build and maintain the source code
+held within. They make the code freely available in the hope that it will be of use to other developers. It would be
+extremely unfair for them to suffer abuse or anger for their hard work.
+
+Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
+world that developers are civilized and selfless people.
+
+It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
+quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
+
+## Viability
+
+When requesting or submitting new features, first consider whether it might be useful to others. Open
+source projects are used by many developers, who may have entirely different needs to your own. Think about
+whether or not your feature is likely to be used by other users of the project.
+
+## Procedure
+
+Before filing an issue:
+
+- Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
+- Check to make sure your feature suggestion isn't already present within the project.
+- Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
+- Check the pull requests tab to ensure that the feature isn't already in progress.
+
+Before submitting a pull request:
+
+- Check the codebase to ensure that your feature doesn't already exist.
+- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
+
+## Requirements
+
+If the project maintainer has any additional requirements, you will find them listed here.
+
+- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer).
+
+- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
+
+- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
+
+- **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
+
+- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
+
+- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
+
+**Happy coding**!

+ 21 - 0
api/vendor/spatie/async/LICENSE.md

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Spatie bvba <info@spatie.be>
+
+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.

+ 336 - 0
api/vendor/spatie/async/README.md

@@ -0,0 +1,336 @@
+# Asynchronous and parallel PHP
+
+[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/async.svg?style=flat-square)](https://packagist.org/packages/spatie/async)
+[![Build Status](https://img.shields.io/travis/spatie/async/master.svg?style=flat-square)](https://travis-ci.org/spatie/async)
+[![Quality Score](https://img.shields.io/scrutinizer/g/spatie/async.svg?style=flat-square)](https://scrutinizer-ci.com/g/spatie/async)
+[![StyleCI](https://styleci.io/repos/114228700/shield?branch=master)](https://styleci.io/repos/114228700)
+[![Total Downloads](https://img.shields.io/packagist/dt/spatie/async.svg?style=flat-square)](https://packagist.org/packages/spatie/async)
+
+This library provides a small and easy wrapper around PHP's PCNTL extension.
+It allows running of different processes in parallel, with an easy-to-use API.
+
+## Support us
+
+[<img src="https://github-ads.s3.eu-central-1.amazonaws.com/async.jpg?t=1" width="419px" />](https://spatie.be/github-ad-click/async)
+
+We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).
+
+We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).
+
+## Installation
+
+You can install the package via composer:
+
+```bash
+composer require spatie/async
+```
+
+## Usage
+
+```php
+use Spatie\Async\Pool;
+
+$pool = Pool::create();
+
+foreach ($things as $thing) {
+    $pool->add(function () use ($thing) {
+        // Do a thing
+    })->then(function ($output) {
+        // Handle success
+    })->catch(function (Throwable $exception) {
+        // Handle exception
+    });
+}
+
+$pool->wait();
+```
+
+### Event listeners
+
+When creating asynchronous processes, you'll get an instance of `ParallelProcess` returned.
+You can add the following event hooks on a process.
+
+```php
+$pool
+    ->add(function () {
+        // ...
+    })
+    ->then(function ($output) {
+        // On success, `$output` is returned by the process or callable you passed to the queue.
+    })
+    ->catch(function ($exception) {
+        // When an exception is thrown from within a process, it's caught and passed here.
+    })
+    ->timeout(function () {
+        // A process took too long to finish.
+    })
+;
+```
+
+### Functional API
+
+Instead of using methods on the `$pool` object, you may also use the `async` and `await` helper functions.
+
+```php
+use Spatie\Async\Pool;
+
+$pool = Pool::create();
+
+foreach (range(1, 5) as $i) {
+    $pool[] = async(function () {
+        usleep(random_int(10, 1000));
+
+        return 2;
+    })->then(function (int $output) {
+        $this->counter += $output;
+    });
+}
+
+await($pool);
+```
+
+### Error handling
+
+If an `Exception` or `Error` is thrown from within a child process, it can be caught per process by specifying a callback in the `->catch()` method.
+
+```php
+$pool
+    ->add(function () {
+        // ...
+    })
+    ->catch(function ($exception) {
+        // Handle the thrown exception for this child process.
+    })
+;
+```
+
+If there's no error handler added, the error will be thrown in the parent process when calling `await()` or `$pool->wait()`.
+
+If the child process would unexpectedly stop without throwing an `Throwable`, 
+the output written to `stderr` will be wrapped and thrown as `Spatie\Async\ParallelError` in the parent process.
+
+### Catching exceptions by type
+
+By type hinting the `catch` functions, you can provide multiple error handlers, 
+each for individual types of errors.
+
+```php
+$pool
+    ->add(function () {
+        throw new MyException('test');
+    })
+    ->catch(function (MyException $e) {
+        // Handle `MyException`
+    })
+    ->catch(function (OtherException $e) {
+        // Handle `OtherException`
+    });
+```
+
+Note that as soon as an exception is handled, it won't trigger any other handlers
+
+```php
+$pool
+    ->add(function () {
+        throw new MyException('test');
+    })
+    ->catch(function (MyException $e) {
+        // This one is triggerd when `MyException` is thrown
+    })
+    ->catch(function (Exception $e) {
+        // This one is not triggerd, even though `MyException` extends `Exception`
+    });
+```
+
+### Stopping a pool
+
+If you need to stop a pool early, because the task it was performing has been completed by one
+of the child processes, you can use the `$pool->stop()` method. This will prevent the 
+pool from starting any additional processes.
+
+```php
+use Spatie\Async\Pool;
+
+$pool = Pool::create();
+
+// Generate 10k processes generating random numbers
+for($i = 0; $i < 10000; $i++) {
+    $pool->add(function() use ($i) {
+        return rand(0, 100);
+    })->then(function($output) use ($pool) {
+        // If one of them randomly picks 100, end the pool early.
+        if ($output === 100) {
+            $pool->stop();
+        }
+    });
+}
+
+$pool->wait();
+```
+
+Note that a pool will be rendered useless after being stopped, and a new pool should be
+created if needed.
+
+### Using another PHP binary
+
+By default the pool will use `php` to execute its child processes. You can configure another binary like so:
+
+```php
+Pool::create()
+    ->withBinary('/path/to/php');
+```
+
+### Working with tasks
+
+Besides using closures, you can also work with a `Task`. 
+A `Task` is useful in situations where you need more setup work in the child process.
+Because a child process is always bootstrapped from nothing, chances are you'll want to initialise eg. the dependency container before executing the task.
+The `Task` class makes this easier to do.
+
+```php
+use Spatie\Async\Task;
+
+class MyTask extends Task
+{
+    public function configure()
+    {
+        // Setup eg. dependency container, load config,...
+    }
+
+    public function run()
+    {
+        // Do the real work here.
+    }
+}
+
+// Add the task to the pool
+$pool->add(new MyTask());
+```
+
+#### Simple tasks
+
+If you want to encapsulate the logic of your task, but don't want to create a full blown `Task` object,
+you may also pass an invokable object to the `Pool`.
+
+```php
+class InvokableClass
+{
+    // ...
+
+    public function __invoke()
+    {
+        // ...
+    }
+}
+
+$pool->add(new InvokableClass(/* ... */));
+```
+
+### Pool configuration
+
+You're free to create as many pools as you want, each pool has its own queue of processes it will handle.
+
+A pool is configurable by the developer:
+
+```php
+use Spatie\Async\Pool;
+
+$pool = Pool::create()
+
+// The maximum amount of processes which can run simultaneously.
+    ->concurrency(20)
+
+// The maximum amount of time a process may take to finish in seconds
+// (decimal places are supported for more granular timeouts).
+    ->timeout(15)
+
+// Configure which autoloader sub processes should use.
+    ->autoload(__DIR__ . '/../../vendor/autoload.php')
+    
+// Configure how long the loop should sleep before re-checking the process statuses in microseconds.
+    ->sleepTime(50000)
+;
+```
+
+### Synchronous fallback
+
+If the required extensions (`pcntl` and `posix`) are not installed in your current PHP runtime, the `Pool` will automatically fallback to synchronous execution of tasks.
+
+The `Pool` class has a static method `isSupported` you can call to check whether your platform is able to run asynchronous processes. 
+
+If you're using a `Task` to run processes, only the `run` method of those tasks will be called when running in synchronous mode.
+
+## Behind the curtains
+
+When using this package, you're probably wondering what's happening underneath the surface.
+
+We're using the `symfony/process` component to create and manage child processes in PHP.
+By creating child processes on the fly, we're able to execute PHP scripts in parallel.
+This parallelism can improve performance significantly when dealing with multiple synchronous tasks,
+which don't really need to wait for each other.
+By giving these tasks a separate process to run on, the underlying operating system can take care of running them in parallel.
+
+There's a caveat when dynamically spawning processes: you need to make sure that there won't be too many processes at once,
+or the application might crash.
+The `Pool` class provided by this package takes care of handling as many processes as you want
+by scheduling and running them when it's possible.
+
+That's the part that `async()` or `$pool->add()` does. Now let's look at what `await()` or `$pool->wait()` does.
+
+When multiple processes are spawned, each can have a separate time to completion.
+One process might eg. have to wait for a HTTP call, while the other has to process large amounts of data.
+Sometimes you also have points in your code which have to wait until the result of a process is returned.
+
+This is why we have to wait at a certain point in time: for all processes on a pool to finish,
+so we can be sure it's safe to continue without accidentally killing the child processes which aren't done yet.
+
+Waiting for all processes is done by using a `while` loop, which will wait until all processes are finished.
+Determining when a process is finished is done by using a listener on the `SIGCHLD` signal.
+This signal is emitted when a child process is finished by the OS kernel.
+As of PHP 7.1, there's much better support for listening and handling signals,
+making this approach more performant than eg. using process forks or sockets for communication.
+You can read more about it [here](https://wiki.php.net/rfc/async_signals).
+
+When a process is finished, its success event is triggered, which you can hook into with the `->then()` function.
+Likewise, when a process fails or times out, the loop will update that process' status and move on.
+When all processes are finished, the while loop will see that there's nothing more to wait for, and stop.
+This is the moment your parent process can continue to execute.
+
+### Comparison to other libraries
+
+We've written a blog post containing more information about use cases for this package, as well as making comparisons to other asynchronous PHP libraries like ReactPHP and Amp: [http://stitcher.io/blog/asynchronous-php](http://stitcher.io/blog/asynchronous-php).
+
+## Testing
+
+``` bash
+composer test
+```
+
+## Changelog
+
+Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
+
+## Contributing
+
+Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
+
+### Security
+
+If you discover any security related issues, please email freek@spatie.be instead of using the issue tracker.
+
+## Postcardware
+
+You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.
+
+Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium.
+
+We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards).
+
+## Credits
+
+- [Brent Roose](https://github.com/brendt)
+- [All Contributors](../../contributors)
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

+ 5 - 0
api/vendor/spatie/async/UPGRADING.md

@@ -0,0 +1,5 @@
+## 1.0.0
+
+- Thrown errors can only have one handler. 
+If you have several handlers catching the same exception, only the first will be triggered. 
+Please see the [README](./README.md#error-handling) for more information.

+ 51 - 0
api/vendor/spatie/async/composer.json

@@ -0,0 +1,51 @@
+{
+    "name": "spatie/async",
+    "description": "Asynchronous and parallel PHP with the PCNTL extension",
+    "keywords": [
+        "spatie",
+        "async"
+    ],
+    "homepage": "https://github.com/spatie/async",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Brent Roose",
+            "email": "brent@spatie.be",
+            "homepage": "https://spatie.be",
+            "role": "Developer"
+        }
+    ],
+    "require": {
+        "php": "^7.1 || ^8.0",
+        "opis/closure": "^3.4.2",
+        "symfony/process": "^3.3 || ^4.0 || ^5.0"
+    },
+    "require-dev": {
+        "larapack/dd": "^1.1",
+        "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0",
+        "symfony/stopwatch": "^4.0 || ^5.0"
+    },
+    "suggest": {
+        "ext-pcntl": "Required to use async processes",
+        "ext-posix": "Required to use async processes"
+    },
+    "autoload": {
+        "files": [
+            "src/helpers.php"
+        ],
+        "psr-4": {
+            "Spatie\\Async\\": "src"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Spatie\\Async\\Tests\\": "tests"
+        }
+    },
+    "scripts": {
+        "test": "vendor/bin/phpunit"
+    },
+    "config": {
+        "sort-packages": true
+    }
+}

BIN
api/vendor/spatie/async/docs/benchmarks.png


+ 18 - 0
api/vendor/spatie/async/src/Output/ParallelError.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Spatie\Async\Output;
+
+use Exception;
+
+class ParallelError extends Exception
+{
+    public static function fromException($exception): self
+    {
+        return new self($exception);
+    }
+
+    public static function outputTooLarge(int $bytes): self
+    {
+        return new self("The output returned by this child process is too large. The serialized output may only be $bytes bytes long.");
+    }
+}

+ 31 - 0
api/vendor/spatie/async/src/Output/ParallelException.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Spatie\Async\Output;
+
+class ParallelException extends \Exception
+{
+    /** @var string */
+    protected $originalClass;
+
+    /** @var string */
+    protected $originalTrace;
+
+    public function __construct(string $message, string $originalClass, string $originalTrace)
+    {
+        parent::__construct($message);
+        $this->originalClass = $originalClass;
+        $this->originalTrace = $originalTrace;
+    }
+
+    /** @return string */
+    public function getOriginalClass(): string
+    {
+        return $this->originalClass;
+    }
+
+    /** @return string */
+    public function getOriginalTrace(): string
+    {
+        return $this->originalTrace;
+    }
+}

+ 36 - 0
api/vendor/spatie/async/src/Output/SerializableException.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace Spatie\Async\Output;
+
+use Throwable;
+
+class SerializableException
+{
+    /** @var string */
+    protected $class;
+
+    /** @var string */
+    protected $message;
+
+    /** @var string */
+    protected $trace;
+
+    public function __construct(Throwable $exception)
+    {
+        $this->class = get_class($exception);
+        $this->message = $exception->getMessage();
+        $this->trace = $exception->getTraceAsString();
+    }
+
+    public function asThrowable(): Throwable
+    {
+        try {
+            /** @var Throwable $throwable */
+            $throwable = new $this->class($this->message."\n\n".$this->trace);
+        } catch (Throwable $exception) {
+            $throwable = new ParallelException($this->message, $this->class, $this->trace);
+        }
+
+        return $throwable;
+    }
+}

+ 337 - 0
api/vendor/spatie/async/src/Pool.php

@@ -0,0 +1,337 @@
+<?php
+
+namespace Spatie\Async;
+
+use ArrayAccess;
+use InvalidArgumentException;
+use Spatie\Async\Process\ParallelProcess;
+use Spatie\Async\Process\Runnable;
+use Spatie\Async\Process\SynchronousProcess;
+use Spatie\Async\Runtime\ParentRuntime;
+
+class Pool implements ArrayAccess
+{
+    public static $forceSynchronous = false;
+
+    protected $concurrency = 20;
+    protected $tasksPerProcess = 1;
+    protected $timeout = 300;
+    protected $sleepTime = 50000;
+
+    /** @var \Spatie\Async\Process\Runnable[] */
+    protected $queue = [];
+
+    /** @var \Spatie\Async\Process\Runnable[] */
+    protected $inProgress = [];
+
+    /** @var \Spatie\Async\Process\Runnable[] */
+    protected $finished = [];
+
+    /** @var \Spatie\Async\Process\Runnable[] */
+    protected $failed = [];
+
+    /** @var \Spatie\Async\Process\Runnable[] */
+    protected $timeouts = [];
+
+    protected $results = [];
+
+    protected $status;
+
+    protected $stopped = false;
+
+    protected $binary = PHP_BINARY;
+
+    public function __construct()
+    {
+        if (static::isSupported()) {
+            $this->registerListener();
+        }
+
+        $this->status = new PoolStatus($this);
+    }
+
+    /**
+     * @return static
+     */
+    public static function create()
+    {
+        return new static();
+    }
+
+    public static function isSupported(): bool
+    {
+        return
+            function_exists('pcntl_async_signals')
+            && function_exists('posix_kill')
+            && function_exists('proc_open')
+            && ! self::$forceSynchronous;
+    }
+
+    public function forceSynchronous(): self
+    {
+        self::$forceSynchronous = true;
+
+        return $this;
+    }
+
+    public function concurrency(int $concurrency): self
+    {
+        $this->concurrency = $concurrency;
+
+        return $this;
+    }
+
+    public function timeout(float $timeout): self
+    {
+        $this->timeout = $timeout;
+
+        return $this;
+    }
+
+    public function autoload(string $autoloader): self
+    {
+        ParentRuntime::init($autoloader);
+
+        return $this;
+    }
+
+    public function sleepTime(int $sleepTime): self
+    {
+        $this->sleepTime = $sleepTime;
+
+        return $this;
+    }
+
+    public function withBinary(string $binary): self
+    {
+        $this->binary = $binary;
+
+        return $this;
+    }
+
+    public function notify()
+    {
+        if (count($this->inProgress) >= $this->concurrency) {
+            return;
+        }
+
+        $process = array_shift($this->queue);
+
+        if (! $process) {
+            return;
+        }
+
+        $this->putInProgress($process);
+    }
+
+    /**
+     * @param \Spatie\Async\Process\Runnable|callable $process
+     * @param int|null $outputLength
+     *
+     * @return \Spatie\Async\Process\Runnable
+     */
+    public function add($process, ?int $outputLength = null): Runnable
+    {
+        if (! is_callable($process) && ! $process instanceof Runnable) {
+            throw new InvalidArgumentException('The process passed to Pool::add should be callable.');
+        }
+
+        if (! $process instanceof Runnable) {
+            $process = ParentRuntime::createProcess(
+                $process,
+                $outputLength,
+                $this->binary
+            );
+        }
+
+        $this->putInQueue($process);
+
+        return $process;
+    }
+
+    public function wait(?callable $intermediateCallback = null): array
+    {
+        while ($this->inProgress) {
+            foreach ($this->inProgress as $process) {
+                if ($process->getCurrentExecutionTime() > $this->timeout) {
+                    $this->markAsTimedOut($process);
+                }
+
+                if ($process instanceof SynchronousProcess) {
+                    $this->markAsFinished($process);
+                }
+            }
+
+            if (! $this->inProgress) {
+                break;
+            }
+
+            if ($intermediateCallback) {
+                call_user_func_array($intermediateCallback, [$this]);
+            }
+
+            usleep($this->sleepTime);
+        }
+
+        return $this->results;
+    }
+
+    public function putInQueue(Runnable $process)
+    {
+        $this->queue[$process->getId()] = $process;
+
+        $this->notify();
+    }
+
+    public function putInProgress(Runnable $process)
+    {
+        if ($this->stopped) {
+            return;
+        }
+
+        if ($process instanceof ParallelProcess) {
+            $process->getProcess()->setTimeout($this->timeout);
+        }
+
+        $process->start();
+
+        unset($this->queue[$process->getId()]);
+
+        $this->inProgress[$process->getPid()] = $process;
+    }
+
+    public function markAsFinished(Runnable $process)
+    {
+        unset($this->inProgress[$process->getPid()]);
+
+        $this->notify();
+
+        $this->results[] = $process->triggerSuccess();
+
+        $this->finished[$process->getPid()] = $process;
+    }
+
+    public function markAsTimedOut(Runnable $process)
+    {
+        unset($this->inProgress[$process->getPid()]);
+
+        $process->stop();
+
+        $process->triggerTimeout();
+        $this->timeouts[$process->getPid()] = $process;
+
+        $this->notify();
+    }
+
+    public function markAsFailed(Runnable $process)
+    {
+        unset($this->inProgress[$process->getPid()]);
+
+        $this->notify();
+
+        $process->triggerError();
+
+        $this->failed[$process->getPid()] = $process;
+    }
+
+    public function offsetExists($offset)
+    {
+        // TODO
+
+        return false;
+    }
+
+    public function offsetGet($offset)
+    {
+        // TODO
+    }
+
+    public function offsetSet($offset, $value)
+    {
+        $this->add($value);
+    }
+
+    public function offsetUnset($offset)
+    {
+        // TODO
+    }
+
+    /**
+     * @return \Spatie\Async\Process\Runnable[]
+     */
+    public function getQueue(): array
+    {
+        return $this->queue;
+    }
+
+    /**
+     * @return \Spatie\Async\Process\Runnable[]
+     */
+    public function getInProgress(): array
+    {
+        return $this->inProgress;
+    }
+
+    /**
+     * @return \Spatie\Async\Process\Runnable[]
+     */
+    public function getFinished(): array
+    {
+        return $this->finished;
+    }
+
+    /**
+     * @return \Spatie\Async\Process\Runnable[]
+     */
+    public function getFailed(): array
+    {
+        return $this->failed;
+    }
+
+    /**
+     * @return \Spatie\Async\Process\Runnable[]
+     */
+    public function getTimeouts(): array
+    {
+        return $this->timeouts;
+    }
+
+    public function status(): PoolStatus
+    {
+        return $this->status;
+    }
+
+    protected function registerListener()
+    {
+        pcntl_async_signals(true);
+
+        pcntl_signal(SIGCHLD, function ($signo, $status) {
+            while (true) {
+                $pid = pcntl_waitpid(-1, $processState, WNOHANG | WUNTRACED);
+
+                if ($pid <= 0) {
+                    break;
+                }
+
+                $process = $this->inProgress[$pid] ?? null;
+
+                if (! $process) {
+                    continue;
+                }
+
+                if ($status['status'] === 0) {
+                    $this->markAsFinished($process);
+
+                    continue;
+                }
+
+                $this->markAsFailed($process);
+            }
+        });
+    }
+
+    public function stop()
+    {
+        $this->stopped = true;
+    }
+}

+ 56 - 0
api/vendor/spatie/async/src/PoolStatus.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace Spatie\Async;
+
+use Spatie\Async\Output\SerializableException;
+use Spatie\Async\Process\ParallelProcess;
+
+class PoolStatus
+{
+    protected $pool;
+
+    public function __construct(Pool $pool)
+    {
+        $this->pool = $pool;
+    }
+
+    public function __toString(): string
+    {
+        return $this->lines(
+            $this->summaryToString(),
+            $this->failedToString()
+        );
+    }
+
+    protected function lines(string ...$lines): string
+    {
+        return implode(PHP_EOL, $lines);
+    }
+
+    protected function summaryToString(): string
+    {
+        $queue = $this->pool->getQueue();
+        $finished = $this->pool->getFinished();
+        $failed = $this->pool->getFailed();
+        $timeouts = $this->pool->getTimeouts();
+
+        return
+            'queue: '.count($queue)
+            .' - finished: '.count($finished)
+            .' - failed: '.count($failed)
+            .' - timeout: '.count($timeouts);
+    }
+
+    protected function failedToString(): string
+    {
+        return (string) array_reduce($this->pool->getFailed(), function ($currentStatus, ParallelProcess $process) {
+            $output = $process->getErrorOutput();
+
+            if ($output instanceof SerializableException) {
+                $output = get_class($output->asThrowable()).': '.$output->asThrowable()->getMessage();
+            }
+
+            return $this->lines((string) $currentStatus, "{$process->getPid()} failed with {$output}");
+        });
+    }
+}

+ 131 - 0
api/vendor/spatie/async/src/Process/ParallelProcess.php

@@ -0,0 +1,131 @@
+<?php
+
+namespace Spatie\Async\Process;
+
+use Spatie\Async\Output\ParallelError;
+use Spatie\Async\Output\SerializableException;
+use Symfony\Component\Process\Process;
+use Throwable;
+
+class ParallelProcess implements Runnable
+{
+    protected $process;
+    protected $id;
+    protected $pid;
+
+    protected $output;
+    protected $errorOutput;
+
+    protected $startTime;
+
+    use ProcessCallbacks;
+
+    public function __construct(Process $process, int $id)
+    {
+        $this->process = $process;
+        $this->id = $id;
+    }
+
+    public static function create(Process $process, int $id): self
+    {
+        return new self($process, $id);
+    }
+
+    public function start(): self
+    {
+        $this->startTime = microtime(true);
+
+        $this->process->start();
+
+        $this->pid = $this->process->getPid();
+
+        return $this;
+    }
+
+    public function stop($timeout = 0): self
+    {
+        $this->process->stop($timeout, SIGKILL);
+
+        return $this;
+    }
+
+    public function isRunning(): bool
+    {
+        return $this->process->isRunning();
+    }
+
+    public function isSuccessful(): bool
+    {
+        return $this->process->isSuccessful();
+    }
+
+    public function isTerminated(): bool
+    {
+        return $this->process->isTerminated();
+    }
+
+    public function getOutput()
+    {
+        if (! $this->output) {
+            $processOutput = $this->process->getOutput();
+
+            $this->output = @unserialize(base64_decode($processOutput));
+
+            if (! $this->output) {
+                $this->errorOutput = $processOutput;
+            }
+        }
+
+        return $this->output;
+    }
+
+    public function getErrorOutput()
+    {
+        if (! $this->errorOutput) {
+            $processOutput = $this->process->getErrorOutput();
+
+            $this->errorOutput = @unserialize(base64_decode($processOutput));
+
+            if (! $this->errorOutput) {
+                $this->errorOutput = $processOutput;
+            }
+        }
+
+        return $this->errorOutput;
+    }
+
+    public function getProcess(): Process
+    {
+        return $this->process;
+    }
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function getPid(): ?int
+    {
+        return $this->pid;
+    }
+
+    public function getCurrentExecutionTime(): float
+    {
+        return microtime(true) - $this->startTime;
+    }
+
+    protected function resolveErrorOutput(): Throwable
+    {
+        $exception = $this->getErrorOutput();
+
+        if ($exception instanceof SerializableException) {
+            $exception = $exception->asThrowable();
+        }
+
+        if (! $exception instanceof Throwable) {
+            $exception = ParallelError::fromException($exception);
+        }
+
+        return $exception;
+    }
+}

+ 108 - 0
api/vendor/spatie/async/src/Process/ProcessCallbacks.php

@@ -0,0 +1,108 @@
+<?php
+
+namespace Spatie\Async\Process;
+
+use ReflectionFunction;
+use Throwable;
+
+trait ProcessCallbacks
+{
+    protected $successCallbacks = [];
+    protected $errorCallbacks = [];
+    protected $timeoutCallbacks = [];
+
+    public function then(callable $callback): self
+    {
+        $this->successCallbacks[] = $callback;
+
+        return $this;
+    }
+
+    public function catch(callable $callback): self
+    {
+        $this->errorCallbacks[] = $callback;
+
+        return $this;
+    }
+
+    public function timeout(callable $callback): self
+    {
+        $this->timeoutCallbacks[] = $callback;
+
+        return $this;
+    }
+
+    public function triggerSuccess()
+    {
+        if ($this->getErrorOutput()) {
+            $this->triggerError();
+
+            return;
+        }
+
+        $output = $this->getOutput();
+
+        foreach ($this->successCallbacks as $callback) {
+            call_user_func_array($callback, [$output]);
+        }
+
+        return $output;
+    }
+
+    public function triggerError()
+    {
+        $exception = $this->resolveErrorOutput();
+
+        if (! $this->errorCallbacks) {
+            throw $exception;
+        }
+
+        foreach ($this->errorCallbacks as $callback) {
+            if (! $this->isAllowedThrowableType($exception, $callback)) {
+                continue;
+            }
+
+            call_user_func_array($callback, [$exception]);
+
+            break;
+        }
+    }
+
+    abstract protected function resolveErrorOutput(): Throwable;
+
+    public function triggerTimeout()
+    {
+        foreach ($this->timeoutCallbacks as $callback) {
+            call_user_func_array($callback, []);
+        }
+    }
+
+    protected function isAllowedThrowableType(Throwable $throwable, callable $callable): bool
+    {
+        $reflection = new ReflectionFunction($callable);
+
+        $parameters = $reflection->getParameters();
+
+        if (! isset($parameters[0])) {
+            return true;
+        }
+
+        $firstParameter = $parameters[0];
+
+        if (! $firstParameter) {
+            return true;
+        }
+
+        $type = $firstParameter->getType();
+
+        if (! $type) {
+            return true;
+        }
+
+        if (is_a($throwable, $type->getName())) {
+            return true;
+        }
+
+        return false;
+    }
+}

+ 52 - 0
api/vendor/spatie/async/src/Process/Runnable.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace Spatie\Async\Process;
+
+interface Runnable
+{
+    public function getId(): int;
+
+    public function getPid(): ?int;
+
+    public function start();
+
+    /**
+     * @param callable $callback
+     *
+     * @return static
+     */
+    public function then(callable $callback);
+
+    /**
+     * @param callable $callback
+     *
+     * @return static
+     */
+    public function catch(callable $callback);
+
+    /**
+     * @param callable $callback
+     *
+     * @return static
+     */
+    public function timeout(callable $callback);
+
+    /**
+     * @param int|float $timeout The timeout in seconds
+     *
+     * @return mixed
+     */
+    public function stop($timeout = 0);
+
+    public function getOutput();
+
+    public function getErrorOutput();
+
+    public function triggerSuccess();
+
+    public function triggerError();
+
+    public function triggerTimeout();
+
+    public function getCurrentExecutionTime(): float;
+}

+ 83 - 0
api/vendor/spatie/async/src/Process/SynchronousProcess.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace Spatie\Async\Process;
+
+use Spatie\Async\Task;
+use Throwable;
+
+class SynchronousProcess implements Runnable
+{
+    protected $id;
+
+    protected $task;
+
+    protected $output;
+    protected $errorOutput;
+    protected $executionTime;
+
+    use ProcessCallbacks;
+
+    public function __construct(callable $task, int $id)
+    {
+        $this->id = $id;
+        $this->task = $task;
+    }
+
+    public static function create(callable $task, int $id): self
+    {
+        return new self($task, $id);
+    }
+
+    public function getId(): int
+    {
+        return $this->id;
+    }
+
+    public function getPid(): ?int
+    {
+        return $this->getId();
+    }
+
+    public function start()
+    {
+        $startTime = microtime(true);
+
+        if ($this->task instanceof Task) {
+            $this->task->configure();
+        }
+
+        try {
+            $this->output = $this->task instanceof Task
+                ? $this->task->run()
+                : call_user_func($this->task);
+        } catch (Throwable $throwable) {
+            $this->errorOutput = $throwable;
+        } finally {
+            $this->executionTime = microtime(true) - $startTime;
+        }
+    }
+
+    public function stop($timeout = 0): void
+    {
+    }
+
+    public function getOutput()
+    {
+        return $this->output;
+    }
+
+    public function getErrorOutput()
+    {
+        return $this->errorOutput;
+    }
+
+    public function getCurrentExecutionTime(): float
+    {
+        return $this->executionTime;
+    }
+
+    protected function resolveErrorOutput(): Throwable
+    {
+        return $this->getErrorOutput();
+    }
+}

+ 45 - 0
api/vendor/spatie/async/src/Runtime/ChildRuntime.php

@@ -0,0 +1,45 @@
+<?php
+
+use Spatie\Async\Runtime\ParentRuntime;
+
+try {
+    $autoloader = $argv[1] ?? null;
+    $serializedClosure = $argv[2] ?? null;
+    $outputLength = $argv[3] ? intval($argv[3]) : (1024 * 10);
+
+    if (! $autoloader) {
+        throw new InvalidArgumentException('No autoloader provided in child process.');
+    }
+
+    if (! file_exists($autoloader)) {
+        throw new InvalidArgumentException("Could not find autoloader in child process: {$autoloader}");
+    }
+
+    if (! $serializedClosure) {
+        throw new InvalidArgumentException('No valid closure was passed to the child process.');
+    }
+
+    require_once $autoloader;
+
+    $task = ParentRuntime::decodeTask($serializedClosure);
+
+    $output = call_user_func($task);
+
+    $serializedOutput = base64_encode(serialize($output));
+
+    if (strlen($serializedOutput) > $outputLength) {
+        throw \Spatie\Async\Output\ParallelError::outputTooLarge($outputLength);
+    }
+
+    fwrite(STDOUT, $serializedOutput);
+
+    exit(0);
+} catch (Throwable $exception) {
+    require_once __DIR__.'/../Output/SerializableException.php';
+
+    $output = new \Spatie\Async\Output\SerializableException($exception);
+
+    fwrite(STDERR, base64_encode(serialize($output)));
+
+    exit(1);
+}

+ 107 - 0
api/vendor/spatie/async/src/Runtime/ParentRuntime.php

@@ -0,0 +1,107 @@
+<?php
+
+namespace Spatie\Async\Runtime;
+
+use Closure;
+use Opis\Closure\SerializableClosure;
+use function Opis\Closure\serialize;
+use function Opis\Closure\unserialize;
+use Spatie\Async\Pool;
+use Spatie\Async\Process\ParallelProcess;
+use Spatie\Async\Process\Runnable;
+use Spatie\Async\Process\SynchronousProcess;
+use Symfony\Component\Process\Process;
+
+class ParentRuntime
+{
+    /** @var bool */
+    protected static $isInitialised = false;
+
+    /** @var string */
+    protected static $autoloader;
+
+    /** @var string */
+    protected static $childProcessScript;
+
+    protected static $currentId = 0;
+
+    protected static $myPid = null;
+
+    public static function init(string $autoloader = null)
+    {
+        if (! $autoloader) {
+            $existingAutoloaderFiles = array_filter([
+                __DIR__.'/../../../../autoload.php',
+                __DIR__.'/../../../autoload.php',
+                __DIR__.'/../../vendor/autoload.php',
+                __DIR__.'/../../../vendor/autoload.php',
+            ], function (string $path) {
+                return file_exists($path);
+            });
+
+            $autoloader = reset($existingAutoloaderFiles);
+        }
+
+        self::$autoloader = $autoloader;
+        self::$childProcessScript = __DIR__.'/ChildRuntime.php';
+
+        self::$isInitialised = true;
+    }
+
+    /**
+     * @param \Spatie\Async\Task|callable $task
+     * @param int|null $outputLength
+     *
+     * @return \Spatie\Async\Process\Runnable
+     */
+    public static function createProcess($task, ?int $outputLength = null, ?string $binary = 'php'): Runnable
+    {
+        if (! self::$isInitialised) {
+            self::init();
+        }
+
+        if (! Pool::isSupported()) {
+            return SynchronousProcess::create($task, self::getId());
+        }
+
+        $process = new Process([
+            $binary,
+            self::$childProcessScript,
+            self::$autoloader,
+            self::encodeTask($task),
+            $outputLength,
+        ]);
+
+        return ParallelProcess::create($process, self::getId());
+    }
+
+    /**
+     * @param \Spatie\Async\Task|callable $task
+     *
+     * @return string
+     */
+    public static function encodeTask($task): string
+    {
+        if ($task instanceof Closure) {
+            $task = new SerializableClosure($task);
+        }
+
+        return base64_encode(serialize($task));
+    }
+
+    public static function decodeTask(string $task)
+    {
+        return unserialize(base64_decode($task));
+    }
+
+    protected static function getId(): string
+    {
+        if (self::$myPid === null) {
+            self::$myPid = getmypid();
+        }
+
+        self::$currentId += 1;
+
+        return (string) self::$currentId.(string) self::$myPid;
+    }
+}

+ 17 - 0
api/vendor/spatie/async/src/Task.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Spatie\Async;
+
+abstract class Task
+{
+    abstract public function configure();
+
+    abstract public function run();
+
+    public function __invoke()
+    {
+        $this->configure();
+
+        return $this->run();
+    }
+}

+ 24 - 0
api/vendor/spatie/async/src/helpers.php

@@ -0,0 +1,24 @@
+<?php
+
+use Spatie\Async\Pool;
+use Spatie\Async\Process\Runnable;
+use Spatie\Async\Runtime\ParentRuntime;
+
+if (! function_exists('async')) {
+    /**
+     * @param \Spatie\Async\Task|callable $task
+     *
+     * @return \Spatie\Async\Process\ParallelProcess
+     */
+    function async($task): Runnable
+    {
+        return ParentRuntime::createProcess($task);
+    }
+}
+
+if (! function_exists('await')) {
+    function await(Pool $pool): array
+    {
+        return $pool->wait();
+    }
+}

+ 116 - 0
api/vendor/symfony/process/CHANGELOG.md

@@ -0,0 +1,116 @@
+CHANGELOG
+=========
+
+5.2.0
+-----
+
+ * added `Process::setOptions()` to set `Process` specific options
+ * added option `create_new_console` to allow a subprocess to continue
+   to run after the main script exited, both on Linux and on Windows
+
+5.1.0
+-----
+
+ * added `Process::getStartTime()` to retrieve the start time of the process as float
+
+5.0.0
+-----
+
+ * removed `Process::inheritEnvironmentVariables()`
+ * removed `PhpProcess::setPhpBinary()`
+ * `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell
+ * removed `Process::setCommandLine()`
+
+4.4.0
+-----
+
+ * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited.
+ * added `Process::getLastOutputTime()` method
+
+4.2.0
+-----
+
+ * added the `Process::fromShellCommandline()` to run commands in a shell wrapper
+ * deprecated passing a command as string when creating a `Process` instance
+ * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods
+ * added the `Process::waitUntil()` method to wait for the process only for a
+   specific output, then continue the normal execution of your application
+
+4.1.0
+-----
+
+ * added the `Process::isTtySupported()` method that allows to check for TTY support
+ * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary
+ * added the `ProcessSignaledException` class to properly catch signaled process errors
+
+4.0.0
+-----
+
+ * environment variables will always be inherited
+ * added a second `array $env = []` argument to the `start()`, `run()`,
+   `mustRun()`, and `restart()` methods of the `Process` class
+ * added a second `array $env = []` argument to the `start()` method of the
+   `PhpProcess` class
+ * the `ProcessUtils::escapeArgument()` method has been removed
+ * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()`
+   methods of the `Process` class have been removed
+ * support for passing `proc_open()` options has been removed
+ * removed the `ProcessBuilder` class, use the `Process` class instead
+ * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class
+ * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not
+   supported anymore
+
+3.4.0
+-----
+
+ * deprecated the ProcessBuilder class
+ * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor)
+
+3.3.0
+-----
+
+ * added command line arrays in the `Process` class
+ * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods
+ * deprecated the `ProcessUtils::escapeArgument()` method
+ * deprecated not inheriting environment variables
+ * deprecated configuring `proc_open()` options
+ * deprecated configuring enhanced Windows compatibility
+ * deprecated configuring enhanced sigchild compatibility
+
+2.5.0
+-----
+
+ * added support for PTY mode
+ * added the convenience method "mustRun"
+ * deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
+ * deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
+ * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
+
+2.4.0
+-----
+
+ * added the ability to define an idle timeout
+
+2.3.0
+-----
+
+ * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
+ * added Process::signal()
+ * added Process::getPid()
+ * added support for a TTY mode
+
+2.2.0
+-----
+
+ * added ProcessBuilder::setArguments() to reset the arguments on a builder
+ * added a way to retrieve the standard and error output incrementally
+ * added Process:restart()
+
+2.1.0
+-----
+
+ * added support for non-blocking processes (start(), wait(), isRunning(), stop())
+ * enhanced Windows compatibility
+ * added Process::getExitCodeText() that returns a string representation for
+   the exit code returned by the process
+ * added ProcessBuilder

+ 21 - 0
api/vendor/symfony/process/Exception/ExceptionInterface.php

@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+interface ExceptionInterface extends \Throwable
+{
+}

+ 21 - 0
api/vendor/symfony/process/Exception/InvalidArgumentException.php

@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}

+ 21 - 0
api/vendor/symfony/process/Exception/LogicException.php

@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}

+ 54 - 0
api/vendor/symfony/process/Exception/ProcessFailedException.php

@@ -0,0 +1,54 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessFailedException extends RuntimeException
+{
+    private $process;
+
+    public function __construct(Process $process)
+    {
+        if ($process->isSuccessful()) {
+            throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+        }
+
+        $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+            $process->getCommandLine(),
+            $process->getExitCode(),
+            $process->getExitCodeText(),
+            $process->getWorkingDirectory()
+        );
+
+        if (!$process->isOutputDisabled()) {
+            $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+                $process->getOutput(),
+                $process->getErrorOutput()
+            );
+        }
+
+        parent::__construct($error);
+
+        $this->process = $process;
+    }
+
+    public function getProcess()
+    {
+        return $this->process;
+    }
+}

+ 41 - 0
api/vendor/symfony/process/Exception/ProcessSignaledException.php

@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process has been signaled.
+ *
+ * @author Sullivan Senechal <soullivaneuh@gmail.com>
+ */
+final class ProcessSignaledException extends RuntimeException
+{
+    private $process;
+
+    public function __construct(Process $process)
+    {
+        $this->process = $process;
+
+        parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
+    }
+
+    public function getProcess(): Process
+    {
+        return $this->process;
+    }
+
+    public function getSignal(): int
+    {
+        return $this->getProcess()->getTermSignal();
+    }
+}

+ 69 - 0
api/vendor/symfony/process/Exception/ProcessTimedOutException.php

@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+    public const TYPE_GENERAL = 1;
+    public const TYPE_IDLE = 2;
+
+    private $process;
+    private $timeoutType;
+
+    public function __construct(Process $process, int $timeoutType)
+    {
+        $this->process = $process;
+        $this->timeoutType = $timeoutType;
+
+        parent::__construct(sprintf(
+            'The process "%s" exceeded the timeout of %s seconds.',
+            $process->getCommandLine(),
+            $this->getExceededTimeout()
+        ));
+    }
+
+    public function getProcess()
+    {
+        return $this->process;
+    }
+
+    public function isGeneralTimeout()
+    {
+        return self::TYPE_GENERAL === $this->timeoutType;
+    }
+
+    public function isIdleTimeout()
+    {
+        return self::TYPE_IDLE === $this->timeoutType;
+    }
+
+    public function getExceededTimeout()
+    {
+        switch ($this->timeoutType) {
+            case self::TYPE_GENERAL:
+                return $this->process->getTimeout();
+
+            case self::TYPE_IDLE:
+                return $this->process->getIdleTimeout();
+
+            default:
+                throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+        }
+    }
+}

+ 21 - 0
api/vendor/symfony/process/Exception/RuntimeException.php

@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * RuntimeException for the Process Component.
+ *
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}

+ 86 - 0
api/vendor/symfony/process/ExecutableFinder.php

@@ -0,0 +1,86 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class ExecutableFinder
+{
+    private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
+
+    /**
+     * Replaces default suffixes of executable.
+     */
+    public function setSuffixes(array $suffixes)
+    {
+        $this->suffixes = $suffixes;
+    }
+
+    /**
+     * Adds new possible suffix to check for executable.
+     */
+    public function addSuffix(string $suffix)
+    {
+        $this->suffixes[] = $suffix;
+    }
+
+    /**
+     * Finds an executable by name.
+     *
+     * @param string      $name      The executable name (without the extension)
+     * @param string|null $default   The default to return if no executable is found
+     * @param array       $extraDirs Additional dirs to check into
+     *
+     * @return string|null The executable path or default value
+     */
+    public function find(string $name, string $default = null, array $extraDirs = [])
+    {
+        if (ini_get('open_basedir')) {
+            $searchPath = array_merge(explode(\PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
+            $dirs = [];
+            foreach ($searchPath as $path) {
+                // Silencing against https://bugs.php.net/69240
+                if (@is_dir($path)) {
+                    $dirs[] = $path;
+                } else {
+                    if (basename($path) == $name && @is_executable($path)) {
+                        return $path;
+                    }
+                }
+            }
+        } else {
+            $dirs = array_merge(
+                explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+                $extraDirs
+            );
+        }
+
+        $suffixes = [''];
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $pathExt = getenv('PATHEXT');
+            $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
+        }
+        foreach ($suffixes as $suffix) {
+            foreach ($dirs as $dir) {
+                if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
+                    return $file;
+                }
+            }
+        }
+
+        return $default;
+    }
+}

+ 93 - 0
api/vendor/symfony/process/InputStream.php

@@ -0,0 +1,93 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * Provides a way to continuously write to the input of a Process until the InputStream is closed.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class InputStream implements \IteratorAggregate
+{
+    /** @var callable|null */
+    private $onEmpty = null;
+    private $input = [];
+    private $open = true;
+
+    /**
+     * Sets a callback that is called when the write buffer becomes empty.
+     */
+    public function onEmpty(callable $onEmpty = null)
+    {
+        $this->onEmpty = $onEmpty;
+    }
+
+    /**
+     * Appends an input to the write buffer.
+     *
+     * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
+     *                                                                stream resource or \Traversable
+     */
+    public function write($input)
+    {
+        if (null === $input) {
+            return;
+        }
+        if ($this->isClosed()) {
+            throw new RuntimeException(sprintf('"%s" is closed.', static::class));
+        }
+        $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
+    }
+
+    /**
+     * Closes the write buffer.
+     */
+    public function close()
+    {
+        $this->open = false;
+    }
+
+    /**
+     * Tells whether the write buffer is closed or not.
+     */
+    public function isClosed()
+    {
+        return !$this->open;
+    }
+
+    /**
+     * @return \Traversable
+     */
+    public function getIterator()
+    {
+        $this->open = true;
+
+        while ($this->open || $this->input) {
+            if (!$this->input) {
+                yield '';
+                continue;
+            }
+            $current = array_shift($this->input);
+
+            if ($current instanceof \Iterator) {
+                yield from $current;
+            } else {
+                yield $current;
+            }
+            if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
+                $this->write($onEmpty($this));
+            }
+        }
+    }
+}

+ 19 - 0
api/vendor/symfony/process/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2004-2021 Fabien Potencier
+
+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.

+ 99 - 0
api/vendor/symfony/process/PhpExecutableFinder.php

@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+class PhpExecutableFinder
+{
+    private $executableFinder;
+
+    public function __construct()
+    {
+        $this->executableFinder = new ExecutableFinder();
+    }
+
+    /**
+     * Finds The PHP executable.
+     *
+     * @return string|false The PHP executable path or false if it cannot be found
+     */
+    public function find(bool $includeArgs = true)
+    {
+        if ($php = getenv('PHP_BINARY')) {
+            if (!is_executable($php)) {
+                $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
+                if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) {
+                    if (!is_executable($php)) {
+                        return false;
+                    }
+                } else {
+                    return false;
+                }
+            }
+
+            return $php;
+        }
+
+        $args = $this->findArguments();
+        $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+        // PHP_BINARY return the current sapi executable
+        if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cgi-fcgi', 'cli', 'cli-server', 'phpdbg'], true)) {
+            return \PHP_BINARY.$args;
+        }
+
+        if ($php = getenv('PHP_PATH')) {
+            if (!@is_executable($php)) {
+                return false;
+            }
+
+            return $php;
+        }
+
+        if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+            if (@is_executable($php)) {
+                return $php;
+            }
+        }
+
+        if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
+            return $php;
+        }
+
+        $dirs = [\PHP_BINDIR];
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $dirs[] = 'C:\xampp\php\\';
+        }
+
+        return $this->executableFinder->find('php', false, $dirs);
+    }
+
+    /**
+     * Finds the PHP executable arguments.
+     *
+     * @return array The PHP executable arguments
+     */
+    public function findArguments()
+    {
+        $arguments = [];
+        if ('phpdbg' === \PHP_SAPI) {
+            $arguments[] = '-qrr';
+        }
+
+        return $arguments;
+    }
+}

+ 72 - 0
api/vendor/symfony/process/PhpProcess.php

@@ -0,0 +1,72 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ *     $p = new PhpProcess('<?php echo "foo"; ?>');
+ *     $p->run();
+ *     print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class PhpProcess extends Process
+{
+    /**
+     * @param string      $script  The PHP script to run (as a string)
+     * @param string|null $cwd     The working directory or null to use the working dir of the current PHP process
+     * @param array|null  $env     The environment variables or null to use the same environment as the current PHP process
+     * @param int         $timeout The timeout in seconds
+     * @param array|null  $php     Path to the PHP binary to use with any additional arguments
+     */
+    public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
+    {
+        if (null === $php) {
+            $executableFinder = new PhpExecutableFinder();
+            $php = $executableFinder->find(false);
+            $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
+        }
+        if ('phpdbg' === \PHP_SAPI) {
+            $file = tempnam(sys_get_temp_dir(), 'dbg');
+            file_put_contents($file, $script);
+            register_shutdown_function('unlink', $file);
+            $php[] = $file;
+            $script = null;
+        }
+
+        parent::__construct($php, $cwd, $env, $script, $timeout);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+    {
+        throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function start(callable $callback = null, array $env = [])
+    {
+        if (null === $this->getCommandLine()) {
+            throw new RuntimeException('Unable to find the PHP executable.');
+        }
+
+        parent::start($callback, $env);
+    }
+}

+ 178 - 0
api/vendor/symfony/process/Pipes/AbstractPipes.php

@@ -0,0 +1,178 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+    public $pipes = [];
+
+    private $inputBuffer = '';
+    private $input;
+    private $blocked = true;
+    private $lastError;
+
+    /**
+     * @param resource|string|int|float|bool|\Iterator|null $input
+     */
+    public function __construct($input)
+    {
+        if (\is_resource($input) || $input instanceof \Iterator) {
+            $this->input = $input;
+        } elseif (\is_string($input)) {
+            $this->inputBuffer = $input;
+        } else {
+            $this->inputBuffer = (string) $input;
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        foreach ($this->pipes as $pipe) {
+            fclose($pipe);
+        }
+        $this->pipes = [];
+    }
+
+    /**
+     * Returns true if a system call has been interrupted.
+     */
+    protected function hasSystemCallBeenInterrupted(): bool
+    {
+        $lastError = $this->lastError;
+        $this->lastError = null;
+
+        // stream_select returns false when the `select` system call is interrupted by an incoming signal
+        return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
+    }
+
+    /**
+     * Unblocks streams.
+     */
+    protected function unblock()
+    {
+        if (!$this->blocked) {
+            return;
+        }
+
+        foreach ($this->pipes as $pipe) {
+            stream_set_blocking($pipe, 0);
+        }
+        if (\is_resource($this->input)) {
+            stream_set_blocking($this->input, 0);
+        }
+
+        $this->blocked = false;
+    }
+
+    /**
+     * Writes input to stdin.
+     *
+     * @throws InvalidArgumentException When an input iterator yields a non supported value
+     */
+    protected function write(): ?array
+    {
+        if (!isset($this->pipes[0])) {
+            return null;
+        }
+        $input = $this->input;
+
+        if ($input instanceof \Iterator) {
+            if (!$input->valid()) {
+                $input = null;
+            } elseif (\is_resource($input = $input->current())) {
+                stream_set_blocking($input, 0);
+            } elseif (!isset($this->inputBuffer[0])) {
+                if (!\is_string($input)) {
+                    if (!is_scalar($input)) {
+                        throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
+                    }
+                    $input = (string) $input;
+                }
+                $this->inputBuffer = $input;
+                $this->input->next();
+                $input = null;
+            } else {
+                $input = null;
+            }
+        }
+
+        $r = $e = [];
+        $w = [$this->pipes[0]];
+
+        // let's have a look if something changed in streams
+        if (false === @stream_select($r, $w, $e, 0, 0)) {
+            return null;
+        }
+
+        foreach ($w as $stdin) {
+            if (isset($this->inputBuffer[0])) {
+                $written = fwrite($stdin, $this->inputBuffer);
+                $this->inputBuffer = substr($this->inputBuffer, $written);
+                if (isset($this->inputBuffer[0])) {
+                    return [$this->pipes[0]];
+                }
+            }
+
+            if ($input) {
+                for (;;) {
+                    $data = fread($input, self::CHUNK_SIZE);
+                    if (!isset($data[0])) {
+                        break;
+                    }
+                    $written = fwrite($stdin, $data);
+                    $data = substr($data, $written);
+                    if (isset($data[0])) {
+                        $this->inputBuffer = $data;
+
+                        return [$this->pipes[0]];
+                    }
+                }
+                if (feof($input)) {
+                    if ($this->input instanceof \Iterator) {
+                        $this->input->next();
+                    } else {
+                        $this->input = null;
+                    }
+                }
+            }
+        }
+
+        // no input to read on resource, buffer is empty
+        if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
+            $this->input = null;
+            fclose($this->pipes[0]);
+            unset($this->pipes[0]);
+        } elseif (!$w) {
+            return [$this->pipes[0]];
+        }
+
+        return null;
+    }
+
+    /**
+     * @internal
+     */
+    public function handleError($type, $msg)
+    {
+        $this->lastError = $msg;
+    }
+}

+ 61 - 0
api/vendor/symfony/process/Pipes/PipesInterface.php

@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+    public const CHUNK_SIZE = 16384;
+
+    /**
+     * Returns an array of descriptors for the use of proc_open.
+     */
+    public function getDescriptors(): array;
+
+    /**
+     * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+     *
+     * @return string[]
+     */
+    public function getFiles(): array;
+
+    /**
+     * Reads data in file handles and pipes.
+     *
+     * @param bool $blocking Whether to use blocking calls or not
+     * @param bool $close    Whether to close pipes if they've reached EOF
+     *
+     * @return string[] An array of read data indexed by their fd
+     */
+    public function readAndWrite(bool $blocking, bool $close = false): array;
+
+    /**
+     * Returns if the current state has open file handles or pipes.
+     */
+    public function areOpen(): bool;
+
+    /**
+     * Returns if pipes are able to read output.
+     */
+    public function haveReadSupport(): bool;
+
+    /**
+     * Closes file handles and pipes.
+     */
+    public function close();
+}

+ 163 - 0
api/vendor/symfony/process/Pipes/UnixPipes.php

@@ -0,0 +1,163 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+    private $ttyMode;
+    private $ptyMode;
+    private $haveReadSupport;
+
+    public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
+    {
+        $this->ttyMode = $ttyMode;
+        $this->ptyMode = $ptyMode;
+        $this->haveReadSupport = $haveReadSupport;
+
+        parent::__construct($input);
+    }
+
+    public function __sleep()
+    {
+        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+    }
+
+    public function __wakeup()
+    {
+        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+    }
+
+    public function __destruct()
+    {
+        $this->close();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDescriptors(): array
+    {
+        if (!$this->haveReadSupport) {
+            $nullstream = fopen('/dev/null', 'c');
+
+            return [
+                ['pipe', 'r'],
+                $nullstream,
+                $nullstream,
+            ];
+        }
+
+        if ($this->ttyMode) {
+            return [
+                ['file', '/dev/tty', 'r'],
+                ['file', '/dev/tty', 'w'],
+                ['file', '/dev/tty', 'w'],
+            ];
+        }
+
+        if ($this->ptyMode && Process::isPtySupported()) {
+            return [
+                ['pty'],
+                ['pty'],
+                ['pty'],
+            ];
+        }
+
+        return [
+            ['pipe', 'r'],
+            ['pipe', 'w'], // stdout
+            ['pipe', 'w'], // stderr
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getFiles(): array
+    {
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function readAndWrite(bool $blocking, bool $close = false): array
+    {
+        $this->unblock();
+        $w = $this->write();
+
+        $read = $e = [];
+        $r = $this->pipes;
+        unset($r[0]);
+
+        // let's have a look if something changed in streams
+        set_error_handler([$this, 'handleError']);
+        if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+            restore_error_handler();
+            // if a system call has been interrupted, forget about it, let's try again
+            // otherwise, an error occurred, let's reset pipes
+            if (!$this->hasSystemCallBeenInterrupted()) {
+                $this->pipes = [];
+            }
+
+            return $read;
+        }
+        restore_error_handler();
+
+        foreach ($r as $pipe) {
+            // prior PHP 5.4 the array passed to stream_select is modified and
+            // lose key association, we have to find back the key
+            $read[$type = array_search($pipe, $this->pipes, true)] = '';
+
+            do {
+                $data = @fread($pipe, self::CHUNK_SIZE);
+                $read[$type] .= $data;
+            } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
+
+            if (!isset($read[$type][0])) {
+                unset($read[$type]);
+            }
+
+            if ($close && feof($pipe)) {
+                fclose($pipe);
+                unset($this->pipes[$type]);
+            }
+        }
+
+        return $read;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function haveReadSupport(): bool
+    {
+        return $this->haveReadSupport;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function areOpen(): bool
+    {
+        return (bool) $this->pipes;
+    }
+}

+ 204 - 0
api/vendor/symfony/process/Pipes/WindowsPipes.php

@@ -0,0 +1,204 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+/**
+ * WindowsPipes implementation uses temporary files as handles.
+ *
+ * @see https://bugs.php.net/51800
+ * @see https://bugs.php.net/65650
+ *
+ * @author Romain Neutron <imprec@gmail.com>
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+    private $files = [];
+    private $fileHandles = [];
+    private $lockHandles = [];
+    private $readBytes = [
+        Process::STDOUT => 0,
+        Process::STDERR => 0,
+    ];
+    private $haveReadSupport;
+
+    public function __construct($input, bool $haveReadSupport)
+    {
+        $this->haveReadSupport = $haveReadSupport;
+
+        if ($this->haveReadSupport) {
+            // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+            // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+            //
+            // @see https://bugs.php.net/51800
+            $pipes = [
+                Process::STDOUT => Process::OUT,
+                Process::STDERR => Process::ERR,
+            ];
+            $tmpDir = sys_get_temp_dir();
+            $lastError = 'unknown reason';
+            set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
+            for ($i = 0;; ++$i) {
+                foreach ($pipes as $pipe => $name) {
+                    $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
+
+                    if (!$h = fopen($file.'.lock', 'w')) {
+                        if (file_exists($file.'.lock')) {
+                            continue 2;
+                        }
+                        restore_error_handler();
+                        throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
+                    }
+                    if (!flock($h, \LOCK_EX | \LOCK_NB)) {
+                        continue 2;
+                    }
+                    if (isset($this->lockHandles[$pipe])) {
+                        flock($this->lockHandles[$pipe], \LOCK_UN);
+                        fclose($this->lockHandles[$pipe]);
+                    }
+                    $this->lockHandles[$pipe] = $h;
+
+                    if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
+                        flock($this->lockHandles[$pipe], \LOCK_UN);
+                        fclose($this->lockHandles[$pipe]);
+                        unset($this->lockHandles[$pipe]);
+                        continue 2;
+                    }
+                    $this->fileHandles[$pipe] = $h;
+                    $this->files[$pipe] = $file;
+                }
+                break;
+            }
+            restore_error_handler();
+        }
+
+        parent::__construct($input);
+    }
+
+    public function __sleep()
+    {
+        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+    }
+
+    public function __wakeup()
+    {
+        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+    }
+
+    public function __destruct()
+    {
+        $this->close();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getDescriptors(): array
+    {
+        if (!$this->haveReadSupport) {
+            $nullstream = fopen('NUL', 'c');
+
+            return [
+                ['pipe', 'r'],
+                $nullstream,
+                $nullstream,
+            ];
+        }
+
+        // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
+        // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
+        // So we redirect output within the commandline and pass the nul device to the process
+        return [
+            ['pipe', 'r'],
+            ['file', 'NUL', 'w'],
+            ['file', 'NUL', 'w'],
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getFiles(): array
+    {
+        return $this->files;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function readAndWrite(bool $blocking, bool $close = false): array
+    {
+        $this->unblock();
+        $w = $this->write();
+        $read = $r = $e = [];
+
+        if ($blocking) {
+            if ($w) {
+                @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
+            } elseif ($this->fileHandles) {
+                usleep(Process::TIMEOUT_PRECISION * 1E6);
+            }
+        }
+        foreach ($this->fileHandles as $type => $fileHandle) {
+            $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
+
+            if (isset($data[0])) {
+                $this->readBytes[$type] += \strlen($data);
+                $read[$type] = $data;
+            }
+            if ($close) {
+                ftruncate($fileHandle, 0);
+                fclose($fileHandle);
+                flock($this->lockHandles[$type], \LOCK_UN);
+                fclose($this->lockHandles[$type]);
+                unset($this->fileHandles[$type], $this->lockHandles[$type]);
+            }
+        }
+
+        return $read;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function haveReadSupport(): bool
+    {
+        return $this->haveReadSupport;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function areOpen(): bool
+    {
+        return $this->pipes && $this->fileHandles;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function close()
+    {
+        parent::close();
+        foreach ($this->fileHandles as $type => $handle) {
+            ftruncate($handle, 0);
+            fclose($handle);
+            flock($this->lockHandles[$type], \LOCK_UN);
+            fclose($this->lockHandles[$type]);
+        }
+        $this->fileHandles = $this->lockHandles = [];
+    }
+}

+ 1674 - 0
api/vendor/symfony/process/Process.php

@@ -0,0 +1,1674 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessSignaledException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Romain Neutron <imprec@gmail.com>
+ */
+class Process implements \IteratorAggregate
+{
+    public const ERR = 'err';
+    public const OUT = 'out';
+
+    public const STATUS_READY = 'ready';
+    public const STATUS_STARTED = 'started';
+    public const STATUS_TERMINATED = 'terminated';
+
+    public const STDIN = 0;
+    public const STDOUT = 1;
+    public const STDERR = 2;
+
+    // Timeout Precision in seconds.
+    public const TIMEOUT_PRECISION = 0.2;
+
+    public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
+    public const ITER_KEEP_OUTPUT = 2;  // By default, outputs are cleared while iterating, use this flag to keep them in memory
+    public const ITER_SKIP_OUT = 4;     // Use this flag to skip STDOUT while iterating
+    public const ITER_SKIP_ERR = 8;     // Use this flag to skip STDERR while iterating
+
+    private $callback;
+    private $hasCallback = false;
+    private $commandline;
+    private $cwd;
+    private $env;
+    private $input;
+    private $starttime;
+    private $lastOutputTime;
+    private $timeout;
+    private $idleTimeout;
+    private $exitcode;
+    private $fallbackStatus = [];
+    private $processInformation;
+    private $outputDisabled = false;
+    private $stdout;
+    private $stderr;
+    private $process;
+    private $status = self::STATUS_READY;
+    private $incrementalOutputOffset = 0;
+    private $incrementalErrorOutputOffset = 0;
+    private $tty = false;
+    private $pty;
+    private $options = ['suppress_errors' => true, 'bypass_shell' => true];
+
+    private $useFileHandles = false;
+    /** @var PipesInterface */
+    private $processPipes;
+
+    private $latestSignal;
+
+    private static $sigchild;
+
+    /**
+     * Exit codes translation table.
+     *
+     * User-defined errors must use exit codes in the 64-113 range.
+     */
+    public static $exitCodes = [
+        0 => 'OK',
+        1 => 'General error',
+        2 => 'Misuse of shell builtins',
+
+        126 => 'Invoked command cannot execute',
+        127 => 'Command not found',
+        128 => 'Invalid exit argument',
+
+        // signals
+        129 => 'Hangup',
+        130 => 'Interrupt',
+        131 => 'Quit and dump core',
+        132 => 'Illegal instruction',
+        133 => 'Trace/breakpoint trap',
+        134 => 'Process aborted',
+        135 => 'Bus error: "access to undefined portion of memory object"',
+        136 => 'Floating point exception: "erroneous arithmetic operation"',
+        137 => 'Kill (terminate immediately)',
+        138 => 'User-defined 1',
+        139 => 'Segmentation violation',
+        140 => 'User-defined 2',
+        141 => 'Write to pipe with no one reading',
+        142 => 'Signal raised by alarm',
+        143 => 'Termination (request to terminate)',
+        // 144 - not defined
+        145 => 'Child process terminated, stopped (or continued*)',
+        146 => 'Continue if stopped',
+        147 => 'Stop executing temporarily',
+        148 => 'Terminal stop signal',
+        149 => 'Background process attempting to read from tty ("in")',
+        150 => 'Background process attempting to write to tty ("out")',
+        151 => 'Urgent data available on socket',
+        152 => 'CPU time limit exceeded',
+        153 => 'File size limit exceeded',
+        154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+        155 => 'Profiling timer expired',
+        // 156 - not defined
+        157 => 'Pollable event',
+        // 158 - not defined
+        159 => 'Bad syscall',
+    ];
+
+    /**
+     * @param array          $command The command to run and its arguments listed as separate entries
+     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
+     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
+     * @param mixed|null     $input   The input as stream resource, scalar or \Traversable, or null for no input
+     * @param int|float|null $timeout The timeout in seconds or null to disable
+     *
+     * @throws LogicException When proc_open is not installed
+     */
+    public function __construct(array $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+    {
+        if (!\function_exists('proc_open')) {
+            throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
+        }
+
+        $this->commandline = $command;
+        $this->cwd = $cwd;
+
+        // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
+        // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
+        // @see : https://bugs.php.net/51800
+        // @see : https://bugs.php.net/50524
+        if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
+            $this->cwd = getcwd();
+        }
+        if (null !== $env) {
+            $this->setEnv($env);
+        }
+
+        $this->setInput($input);
+        $this->setTimeout($timeout);
+        $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
+        $this->pty = false;
+    }
+
+    /**
+     * Creates a Process instance as a command-line to be run in a shell wrapper.
+     *
+     * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
+     * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
+     * shell wrapper and not to your commands.
+     *
+     * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
+     * This will save escaping values, which is not portable nor secure anyway:
+     *
+     *   $process = Process::fromShellCommandline('my_command "$MY_VAR"');
+     *   $process->run(null, ['MY_VAR' => $theValue]);
+     *
+     * @param string         $command The command line to pass to the shell of the OS
+     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
+     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
+     * @param mixed|null     $input   The input as stream resource, scalar or \Traversable, or null for no input
+     * @param int|float|null $timeout The timeout in seconds or null to disable
+     *
+     * @return static
+     *
+     * @throws LogicException When proc_open is not installed
+     */
+    public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+    {
+        $process = new static([], $cwd, $env, $input, $timeout);
+        $process->commandline = $command;
+
+        return $process;
+    }
+
+    public function __sleep()
+    {
+        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+    }
+
+    public function __wakeup()
+    {
+        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+    }
+
+    public function __destruct()
+    {
+        if ($this->options['create_new_console'] ?? false) {
+            $this->processPipes->close();
+        } else {
+            $this->stop(0);
+        }
+    }
+
+    public function __clone()
+    {
+        $this->resetProcessData();
+    }
+
+    /**
+     * Runs the process.
+     *
+     * The callback receives the type of output (out or err) and
+     * some bytes from the output in real-time. It allows to have feedback
+     * from the independent process during execution.
+     *
+     * The STDOUT and STDERR are also available after the process is finished
+     * via the getOutput() and getErrorOutput() methods.
+     *
+     * @param callable|null $callback A PHP callback to run whenever there is some
+     *                                output available on STDOUT or STDERR
+     *
+     * @return int The exit status code
+     *
+     * @throws RuntimeException         When process can't be launched
+     * @throws RuntimeException         When process is already running
+     * @throws ProcessTimedOutException When process timed out
+     * @throws ProcessSignaledException When process stopped after receiving signal
+     * @throws LogicException           In case a callback is provided and output has been disabled
+     *
+     * @final
+     */
+    public function run(callable $callback = null, array $env = []): int
+    {
+        $this->start($callback, $env);
+
+        return $this->wait();
+    }
+
+    /**
+     * Runs the process.
+     *
+     * This is identical to run() except that an exception is thrown if the process
+     * exits with a non-zero exit code.
+     *
+     * @return $this
+     *
+     * @throws ProcessFailedException if the process didn't terminate successfully
+     *
+     * @final
+     */
+    public function mustRun(callable $callback = null, array $env = []): self
+    {
+        if (0 !== $this->run($callback, $env)) {
+            throw new ProcessFailedException($this);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Starts the process and returns after writing the input to STDIN.
+     *
+     * This method blocks until all STDIN data is sent to the process then it
+     * returns while the process runs in the background.
+     *
+     * The termination of the process can be awaited with wait().
+     *
+     * The callback receives the type of output (out or err) and some bytes from
+     * the output in real-time while writing the standard input to the process.
+     * It allows to have feedback from the independent process during execution.
+     *
+     * @param callable|null $callback A PHP callback to run whenever there is some
+     *                                output available on STDOUT or STDERR
+     *
+     * @throws RuntimeException When process can't be launched
+     * @throws RuntimeException When process is already running
+     * @throws LogicException   In case a callback is provided and output has been disabled
+     */
+    public function start(callable $callback = null, array $env = [])
+    {
+        if ($this->isRunning()) {
+            throw new RuntimeException('Process is already running.');
+        }
+
+        $this->resetProcessData();
+        $this->starttime = $this->lastOutputTime = microtime(true);
+        $this->callback = $this->buildCallback($callback);
+        $this->hasCallback = null !== $callback;
+        $descriptors = $this->getDescriptors();
+
+        if ($this->env) {
+            $env += $this->env;
+        }
+
+        $env += $this->getDefaultEnv();
+
+        if (\is_array($commandline = $this->commandline)) {
+            $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
+
+            if ('\\' !== \DIRECTORY_SEPARATOR) {
+                // exec is mandatory to deal with sending a signal to the process
+                $commandline = 'exec '.$commandline;
+            }
+        } else {
+            $commandline = $this->replacePlaceholders($commandline, $env);
+        }
+
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $commandline = $this->prepareWindowsCommandLine($commandline, $env);
+        } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
+            // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+            $descriptors[3] = ['pipe', 'w'];
+
+            // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
+            $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
+            $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
+
+            // Workaround for the bug, when PTS functionality is enabled.
+            // @see : https://bugs.php.net/69442
+            $ptsWorkaround = fopen(__FILE__, 'r');
+        }
+
+        $envPairs = [];
+        foreach ($env as $k => $v) {
+            if (false !== $v) {
+                $envPairs[] = $k.'='.$v;
+            }
+        }
+
+        if (!is_dir($this->cwd)) {
+            throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
+        }
+
+        $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
+
+        if (!\is_resource($this->process)) {
+            throw new RuntimeException('Unable to launch a new process.');
+        }
+        $this->status = self::STATUS_STARTED;
+
+        if (isset($descriptors[3])) {
+            $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
+        }
+
+        if ($this->tty) {
+            return;
+        }
+
+        $this->updateStatus(false);
+        $this->checkTimeout();
+    }
+
+    /**
+     * Restarts the process.
+     *
+     * Be warned that the process is cloned before being started.
+     *
+     * @param callable|null $callback A PHP callback to run whenever there is some
+     *                                output available on STDOUT or STDERR
+     *
+     * @return static
+     *
+     * @throws RuntimeException When process can't be launched
+     * @throws RuntimeException When process is already running
+     *
+     * @see start()
+     *
+     * @final
+     */
+    public function restart(callable $callback = null, array $env = []): self
+    {
+        if ($this->isRunning()) {
+            throw new RuntimeException('Process is already running.');
+        }
+
+        $process = clone $this;
+        $process->start($callback, $env);
+
+        return $process;
+    }
+
+    /**
+     * Waits for the process to terminate.
+     *
+     * The callback receives the type of output (out or err) and some bytes
+     * from the output in real-time while writing the standard input to the process.
+     * It allows to have feedback from the independent process during execution.
+     *
+     * @param callable|null $callback A valid PHP callback
+     *
+     * @return int The exitcode of the process
+     *
+     * @throws ProcessTimedOutException When process timed out
+     * @throws ProcessSignaledException When process stopped after receiving signal
+     * @throws LogicException           When process is not yet started
+     */
+    public function wait(callable $callback = null)
+    {
+        $this->requireProcessIsStarted(__FUNCTION__);
+
+        $this->updateStatus(false);
+
+        if (null !== $callback) {
+            if (!$this->processPipes->haveReadSupport()) {
+                $this->stop(0);
+                throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".');
+            }
+            $this->callback = $this->buildCallback($callback);
+        }
+
+        do {
+            $this->checkTimeout();
+            $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+            $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+        } while ($running);
+
+        while ($this->isRunning()) {
+            $this->checkTimeout();
+            usleep(1000);
+        }
+
+        if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+            throw new ProcessSignaledException($this);
+        }
+
+        return $this->exitcode;
+    }
+
+    /**
+     * Waits until the callback returns true.
+     *
+     * The callback receives the type of output (out or err) and some bytes
+     * from the output in real-time while writing the standard input to the process.
+     * It allows to have feedback from the independent process during execution.
+     *
+     * @throws RuntimeException         When process timed out
+     * @throws LogicException           When process is not yet started
+     * @throws ProcessTimedOutException In case the timeout was reached
+     */
+    public function waitUntil(callable $callback): bool
+    {
+        $this->requireProcessIsStarted(__FUNCTION__);
+        $this->updateStatus(false);
+
+        if (!$this->processPipes->haveReadSupport()) {
+            $this->stop(0);
+            throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
+        }
+        $callback = $this->buildCallback($callback);
+
+        $ready = false;
+        while (true) {
+            $this->checkTimeout();
+            $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+            $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+            foreach ($output as $type => $data) {
+                if (3 !== $type) {
+                    $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
+                } elseif (!isset($this->fallbackStatus['signaled'])) {
+                    $this->fallbackStatus['exitcode'] = (int) $data;
+                }
+            }
+            if ($ready) {
+                return true;
+            }
+            if (!$running) {
+                return false;
+            }
+
+            usleep(1000);
+        }
+    }
+
+    /**
+     * Returns the Pid (process identifier), if applicable.
+     *
+     * @return int|null The process id if running, null otherwise
+     */
+    public function getPid()
+    {
+        return $this->isRunning() ? $this->processInformation['pid'] : null;
+    }
+
+    /**
+     * Sends a POSIX signal to the process.
+     *
+     * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+     *
+     * @return $this
+     *
+     * @throws LogicException   In case the process is not running
+     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+     * @throws RuntimeException In case of failure
+     */
+    public function signal(int $signal)
+    {
+        $this->doSignal($signal, true);
+
+        return $this;
+    }
+
+    /**
+     * Disables fetching output and error output from the underlying process.
+     *
+     * @return $this
+     *
+     * @throws RuntimeException In case the process is already running
+     * @throws LogicException   if an idle timeout is set
+     */
+    public function disableOutput()
+    {
+        if ($this->isRunning()) {
+            throw new RuntimeException('Disabling output while the process is running is not possible.');
+        }
+        if (null !== $this->idleTimeout) {
+            throw new LogicException('Output can not be disabled while an idle timeout is set.');
+        }
+
+        $this->outputDisabled = true;
+
+        return $this;
+    }
+
+    /**
+     * Enables fetching output and error output from the underlying process.
+     *
+     * @return $this
+     *
+     * @throws RuntimeException In case the process is already running
+     */
+    public function enableOutput()
+    {
+        if ($this->isRunning()) {
+            throw new RuntimeException('Enabling output while the process is running is not possible.');
+        }
+
+        $this->outputDisabled = false;
+
+        return $this;
+    }
+
+    /**
+     * Returns true in case the output is disabled, false otherwise.
+     *
+     * @return bool
+     */
+    public function isOutputDisabled()
+    {
+        return $this->outputDisabled;
+    }
+
+    /**
+     * Returns the current output of the process (STDOUT).
+     *
+     * @return string The process output
+     *
+     * @throws LogicException in case the output has been disabled
+     * @throws LogicException In case the process is not started
+     */
+    public function getOutput()
+    {
+        $this->readPipesForOutput(__FUNCTION__);
+
+        if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
+            return '';
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Returns the output incrementally.
+     *
+     * In comparison with the getOutput method which always return the whole
+     * output, this one returns the new output since the last call.
+     *
+     * @return string The process output since the last call
+     *
+     * @throws LogicException in case the output has been disabled
+     * @throws LogicException In case the process is not started
+     */
+    public function getIncrementalOutput()
+    {
+        $this->readPipesForOutput(__FUNCTION__);
+
+        $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+        $this->incrementalOutputOffset = ftell($this->stdout);
+
+        if (false === $latest) {
+            return '';
+        }
+
+        return $latest;
+    }
+
+    /**
+     * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
+     *
+     * @param int $flags A bit field of Process::ITER_* flags
+     *
+     * @throws LogicException in case the output has been disabled
+     * @throws LogicException In case the process is not started
+     *
+     * @return \Generator
+     */
+    public function getIterator(int $flags = 0)
+    {
+        $this->readPipesForOutput(__FUNCTION__, false);
+
+        $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
+        $blocking = !(self::ITER_NON_BLOCKING & $flags);
+        $yieldOut = !(self::ITER_SKIP_OUT & $flags);
+        $yieldErr = !(self::ITER_SKIP_ERR & $flags);
+
+        while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
+            if ($yieldOut) {
+                $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+
+                if (isset($out[0])) {
+                    if ($clearOutput) {
+                        $this->clearOutput();
+                    } else {
+                        $this->incrementalOutputOffset = ftell($this->stdout);
+                    }
+
+                    yield self::OUT => $out;
+                }
+            }
+
+            if ($yieldErr) {
+                $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+
+                if (isset($err[0])) {
+                    if ($clearOutput) {
+                        $this->clearErrorOutput();
+                    } else {
+                        $this->incrementalErrorOutputOffset = ftell($this->stderr);
+                    }
+
+                    yield self::ERR => $err;
+                }
+            }
+
+            if (!$blocking && !isset($out[0]) && !isset($err[0])) {
+                yield self::OUT => '';
+            }
+
+            $this->checkTimeout();
+            $this->readPipesForOutput(__FUNCTION__, $blocking);
+        }
+    }
+
+    /**
+     * Clears the process output.
+     *
+     * @return $this
+     */
+    public function clearOutput()
+    {
+        ftruncate($this->stdout, 0);
+        fseek($this->stdout, 0);
+        $this->incrementalOutputOffset = 0;
+
+        return $this;
+    }
+
+    /**
+     * Returns the current error output of the process (STDERR).
+     *
+     * @return string The process error output
+     *
+     * @throws LogicException in case the output has been disabled
+     * @throws LogicException In case the process is not started
+     */
+    public function getErrorOutput()
+    {
+        $this->readPipesForOutput(__FUNCTION__);
+
+        if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
+            return '';
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Returns the errorOutput incrementally.
+     *
+     * In comparison with the getErrorOutput method which always return the
+     * whole error output, this one returns the new error output since the last
+     * call.
+     *
+     * @return string The process error output since the last call
+     *
+     * @throws LogicException in case the output has been disabled
+     * @throws LogicException In case the process is not started
+     */
+    public function getIncrementalErrorOutput()
+    {
+        $this->readPipesForOutput(__FUNCTION__);
+
+        $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+        $this->incrementalErrorOutputOffset = ftell($this->stderr);
+
+        if (false === $latest) {
+            return '';
+        }
+
+        return $latest;
+    }
+
+    /**
+     * Clears the process output.
+     *
+     * @return $this
+     */
+    public function clearErrorOutput()
+    {
+        ftruncate($this->stderr, 0);
+        fseek($this->stderr, 0);
+        $this->incrementalErrorOutputOffset = 0;
+
+        return $this;
+    }
+
+    /**
+     * Returns the exit code returned by the process.
+     *
+     * @return int|null The exit status code, null if the Process is not terminated
+     */
+    public function getExitCode()
+    {
+        $this->updateStatus(false);
+
+        return $this->exitcode;
+    }
+
+    /**
+     * Returns a string representation for the exit code returned by the process.
+     *
+     * This method relies on the Unix exit code status standardization
+     * and might not be relevant for other operating systems.
+     *
+     * @return string|null A string representation for the exit status code, null if the Process is not terminated
+     *
+     * @see http://tldp.org/LDP/abs/html/exitcodes.html
+     * @see http://en.wikipedia.org/wiki/Unix_signal
+     */
+    public function getExitCodeText()
+    {
+        if (null === $exitcode = $this->getExitCode()) {
+            return null;
+        }
+
+        return self::$exitCodes[$exitcode] ?? 'Unknown error';
+    }
+
+    /**
+     * Checks if the process ended successfully.
+     *
+     * @return bool true if the process ended successfully, false otherwise
+     */
+    public function isSuccessful()
+    {
+        return 0 === $this->getExitCode();
+    }
+
+    /**
+     * Returns true if the child process has been terminated by an uncaught signal.
+     *
+     * It always returns false on Windows.
+     *
+     * @return bool
+     *
+     * @throws LogicException In case the process is not terminated
+     */
+    public function hasBeenSignaled()
+    {
+        $this->requireProcessIsTerminated(__FUNCTION__);
+
+        return $this->processInformation['signaled'];
+    }
+
+    /**
+     * Returns the number of the signal that caused the child process to terminate its execution.
+     *
+     * It is only meaningful if hasBeenSignaled() returns true.
+     *
+     * @return int
+     *
+     * @throws RuntimeException In case --enable-sigchild is activated
+     * @throws LogicException   In case the process is not terminated
+     */
+    public function getTermSignal()
+    {
+        $this->requireProcessIsTerminated(__FUNCTION__);
+
+        if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
+            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
+        }
+
+        return $this->processInformation['termsig'];
+    }
+
+    /**
+     * Returns true if the child process has been stopped by a signal.
+     *
+     * It always returns false on Windows.
+     *
+     * @return bool
+     *
+     * @throws LogicException In case the process is not terminated
+     */
+    public function hasBeenStopped()
+    {
+        $this->requireProcessIsTerminated(__FUNCTION__);
+
+        return $this->processInformation['stopped'];
+    }
+
+    /**
+     * Returns the number of the signal that caused the child process to stop its execution.
+     *
+     * It is only meaningful if hasBeenStopped() returns true.
+     *
+     * @return int
+     *
+     * @throws LogicException In case the process is not terminated
+     */
+    public function getStopSignal()
+    {
+        $this->requireProcessIsTerminated(__FUNCTION__);
+
+        return $this->processInformation['stopsig'];
+    }
+
+    /**
+     * Checks if the process is currently running.
+     *
+     * @return bool true if the process is currently running, false otherwise
+     */
+    public function isRunning()
+    {
+        if (self::STATUS_STARTED !== $this->status) {
+            return false;
+        }
+
+        $this->updateStatus(false);
+
+        return $this->processInformation['running'];
+    }
+
+    /**
+     * Checks if the process has been started with no regard to the current state.
+     *
+     * @return bool true if status is ready, false otherwise
+     */
+    public function isStarted()
+    {
+        return self::STATUS_READY != $this->status;
+    }
+
+    /**
+     * Checks if the process is terminated.
+     *
+     * @return bool true if process is terminated, false otherwise
+     */
+    public function isTerminated()
+    {
+        $this->updateStatus(false);
+
+        return self::STATUS_TERMINATED == $this->status;
+    }
+
+    /**
+     * Gets the process status.
+     *
+     * The status is one of: ready, started, terminated.
+     *
+     * @return string The current process status
+     */
+    public function getStatus()
+    {
+        $this->updateStatus(false);
+
+        return $this->status;
+    }
+
+    /**
+     * Stops the process.
+     *
+     * @param int|float $timeout The timeout in seconds
+     * @param int       $signal  A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
+     *
+     * @return int|null The exit-code of the process or null if it's not running
+     */
+    public function stop(float $timeout = 10, int $signal = null)
+    {
+        $timeoutMicro = microtime(true) + $timeout;
+        if ($this->isRunning()) {
+            // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
+            $this->doSignal(15, false);
+            do {
+                usleep(1000);
+            } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+            if ($this->isRunning()) {
+                // Avoid exception here: process is supposed to be running, but it might have stopped just
+                // after this line. In any case, let's silently discard the error, we cannot do anything.
+                $this->doSignal($signal ?: 9, false);
+            }
+        }
+
+        if ($this->isRunning()) {
+            if (isset($this->fallbackStatus['pid'])) {
+                unset($this->fallbackStatus['pid']);
+
+                return $this->stop(0, $signal);
+            }
+            $this->close();
+        }
+
+        return $this->exitcode;
+    }
+
+    /**
+     * Adds a line to the STDOUT stream.
+     *
+     * @internal
+     */
+    public function addOutput(string $line)
+    {
+        $this->lastOutputTime = microtime(true);
+
+        fseek($this->stdout, 0, \SEEK_END);
+        fwrite($this->stdout, $line);
+        fseek($this->stdout, $this->incrementalOutputOffset);
+    }
+
+    /**
+     * Adds a line to the STDERR stream.
+     *
+     * @internal
+     */
+    public function addErrorOutput(string $line)
+    {
+        $this->lastOutputTime = microtime(true);
+
+        fseek($this->stderr, 0, \SEEK_END);
+        fwrite($this->stderr, $line);
+        fseek($this->stderr, $this->incrementalErrorOutputOffset);
+    }
+
+    /**
+     * Gets the last output time in seconds.
+     *
+     * @return float|null The last output time in seconds or null if it isn't started
+     */
+    public function getLastOutputTime(): ?float
+    {
+        return $this->lastOutputTime;
+    }
+
+    /**
+     * Gets the command line to be executed.
+     *
+     * @return string The command to execute
+     */
+    public function getCommandLine()
+    {
+        return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
+    }
+
+    /**
+     * Gets the process timeout (max. runtime).
+     *
+     * @return float|null The timeout in seconds or null if it's disabled
+     */
+    public function getTimeout()
+    {
+        return $this->timeout;
+    }
+
+    /**
+     * Gets the process idle timeout (max. time since last output).
+     *
+     * @return float|null The timeout in seconds or null if it's disabled
+     */
+    public function getIdleTimeout()
+    {
+        return $this->idleTimeout;
+    }
+
+    /**
+     * Sets the process timeout (max. runtime) in seconds.
+     *
+     * To disable the timeout, set this value to null.
+     *
+     * @return $this
+     *
+     * @throws InvalidArgumentException if the timeout is negative
+     */
+    public function setTimeout(?float $timeout)
+    {
+        $this->timeout = $this->validateTimeout($timeout);
+
+        return $this;
+    }
+
+    /**
+     * Sets the process idle timeout (max. time since last output) in seconds.
+     *
+     * To disable the timeout, set this value to null.
+     *
+     * @return $this
+     *
+     * @throws LogicException           if the output is disabled
+     * @throws InvalidArgumentException if the timeout is negative
+     */
+    public function setIdleTimeout(?float $timeout)
+    {
+        if (null !== $timeout && $this->outputDisabled) {
+            throw new LogicException('Idle timeout can not be set while the output is disabled.');
+        }
+
+        $this->idleTimeout = $this->validateTimeout($timeout);
+
+        return $this;
+    }
+
+    /**
+     * Enables or disables the TTY mode.
+     *
+     * @return $this
+     *
+     * @throws RuntimeException In case the TTY mode is not supported
+     */
+    public function setTty(bool $tty)
+    {
+        if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
+            throw new RuntimeException('TTY mode is not supported on Windows platform.');
+        }
+
+        if ($tty && !self::isTtySupported()) {
+            throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
+        }
+
+        $this->tty = $tty;
+
+        return $this;
+    }
+
+    /**
+     * Checks if the TTY mode is enabled.
+     *
+     * @return bool true if the TTY mode is enabled, false otherwise
+     */
+    public function isTty()
+    {
+        return $this->tty;
+    }
+
+    /**
+     * Sets PTY mode.
+     *
+     * @return $this
+     */
+    public function setPty(bool $bool)
+    {
+        $this->pty = $bool;
+
+        return $this;
+    }
+
+    /**
+     * Returns PTY state.
+     *
+     * @return bool
+     */
+    public function isPty()
+    {
+        return $this->pty;
+    }
+
+    /**
+     * Gets the working directory.
+     *
+     * @return string|null The current working directory or null on failure
+     */
+    public function getWorkingDirectory()
+    {
+        if (null === $this->cwd) {
+            // getcwd() will return false if any one of the parent directories does not have
+            // the readable or search mode set, even if the current directory does
+            return getcwd() ?: null;
+        }
+
+        return $this->cwd;
+    }
+
+    /**
+     * Sets the current working directory.
+     *
+     * @return $this
+     */
+    public function setWorkingDirectory(string $cwd)
+    {
+        $this->cwd = $cwd;
+
+        return $this;
+    }
+
+    /**
+     * Gets the environment variables.
+     *
+     * @return array The current environment variables
+     */
+    public function getEnv()
+    {
+        return $this->env;
+    }
+
+    /**
+     * Sets the environment variables.
+     *
+     * Each environment variable value should be a string.
+     * If it is an array, the variable is ignored.
+     * If it is false or null, it will be removed when
+     * env vars are otherwise inherited.
+     *
+     * That happens in PHP when 'argv' is registered into
+     * the $_ENV array for instance.
+     *
+     * @param array $env The new environment variables
+     *
+     * @return $this
+     */
+    public function setEnv(array $env)
+    {
+        // Process can not handle env values that are arrays
+        $env = array_filter($env, function ($value) {
+            return !\is_array($value);
+        });
+
+        $this->env = $env;
+
+        return $this;
+    }
+
+    /**
+     * Gets the Process input.
+     *
+     * @return resource|string|\Iterator|null The Process input
+     */
+    public function getInput()
+    {
+        return $this->input;
+    }
+
+    /**
+     * Sets the input.
+     *
+     * This content will be passed to the underlying process standard input.
+     *
+     * @param string|int|float|bool|resource|\Traversable|null $input The content
+     *
+     * @return $this
+     *
+     * @throws LogicException In case the process is running
+     */
+    public function setInput($input)
+    {
+        if ($this->isRunning()) {
+            throw new LogicException('Input can not be set while the process is running.');
+        }
+
+        $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+        return $this;
+    }
+
+    /**
+     * Performs a check between the timeout definition and the time the process started.
+     *
+     * In case you run a background process (with the start method), you should
+     * trigger this method regularly to ensure the process timeout
+     *
+     * @throws ProcessTimedOutException In case the timeout was reached
+     */
+    public function checkTimeout()
+    {
+        if (self::STATUS_STARTED !== $this->status) {
+            return;
+        }
+
+        if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+            $this->stop(0);
+
+            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+        }
+
+        if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+            $this->stop(0);
+
+            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+        }
+    }
+
+    /**
+     * @throws LogicException in case process is not started
+     */
+    public function getStartTime(): float
+    {
+        if (!$this->isStarted()) {
+            throw new LogicException('Start time is only available after process start.');
+        }
+
+        return $this->starttime;
+    }
+
+    /**
+     * Defines options to pass to the underlying proc_open().
+     *
+     * @see https://php.net/proc_open for the options supported by PHP.
+     *
+     * Enabling the "create_new_console" option allows a subprocess to continue
+     * to run after the main process exited, on both Windows and *nix
+     */
+    public function setOptions(array $options)
+    {
+        if ($this->isRunning()) {
+            throw new RuntimeException('Setting options while the process is running is not possible.');
+        }
+
+        $defaultOptions = $this->options;
+        $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];
+
+        foreach ($options as $key => $value) {
+            if (!\in_array($key, $existingOptions)) {
+                $this->options = $defaultOptions;
+                throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
+            }
+            $this->options[$key] = $value;
+        }
+    }
+
+    /**
+     * Returns whether TTY is supported on the current operating system.
+     */
+    public static function isTtySupported(): bool
+    {
+        static $isTtySupported;
+
+        if (null === $isTtySupported) {
+            $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
+        }
+
+        return $isTtySupported;
+    }
+
+    /**
+     * Returns whether PTY is supported on the current operating system.
+     *
+     * @return bool
+     */
+    public static function isPtySupported()
+    {
+        static $result;
+
+        if (null !== $result) {
+            return $result;
+        }
+
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            return $result = false;
+        }
+
+        return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
+    }
+
+    /**
+     * Creates the descriptors needed by the proc_open.
+     */
+    private function getDescriptors(): array
+    {
+        if ($this->input instanceof \Iterator) {
+            $this->input->rewind();
+        }
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
+        } else {
+            $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
+        }
+
+        return $this->processPipes->getDescriptors();
+    }
+
+    /**
+     * Builds up the callback used by wait().
+     *
+     * The callbacks adds all occurred output to the specific buffer and calls
+     * the user callback (if present) with the received output.
+     *
+     * @param callable|null $callback The user defined PHP callback
+     *
+     * @return \Closure A PHP closure
+     */
+    protected function buildCallback(callable $callback = null)
+    {
+        if ($this->outputDisabled) {
+            return function ($type, $data) use ($callback): bool {
+                return null !== $callback && $callback($type, $data);
+            };
+        }
+
+        $out = self::OUT;
+
+        return function ($type, $data) use ($callback, $out): bool {
+            if ($out == $type) {
+                $this->addOutput($data);
+            } else {
+                $this->addErrorOutput($data);
+            }
+
+            return null !== $callback && $callback($type, $data);
+        };
+    }
+
+    /**
+     * Updates the status of the process, reads pipes.
+     *
+     * @param bool $blocking Whether to use a blocking read call
+     */
+    protected function updateStatus(bool $blocking)
+    {
+        if (self::STATUS_STARTED !== $this->status) {
+            return;
+        }
+
+        $this->processInformation = proc_get_status($this->process);
+        $running = $this->processInformation['running'];
+
+        $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+        if ($this->fallbackStatus && $this->isSigchildEnabled()) {
+            $this->processInformation = $this->fallbackStatus + $this->processInformation;
+        }
+
+        if (!$running) {
+            $this->close();
+        }
+    }
+
+    /**
+     * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+     *
+     * @return bool
+     */
+    protected function isSigchildEnabled()
+    {
+        if (null !== self::$sigchild) {
+            return self::$sigchild;
+        }
+
+        if (!\function_exists('phpinfo')) {
+            return self::$sigchild = false;
+        }
+
+        ob_start();
+        phpinfo(\INFO_GENERAL);
+
+        return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
+    }
+
+    /**
+     * Reads pipes for the freshest output.
+     *
+     * @param string $caller   The name of the method that needs fresh outputs
+     * @param bool   $blocking Whether to use blocking calls or not
+     *
+     * @throws LogicException in case output has been disabled or process is not started
+     */
+    private function readPipesForOutput(string $caller, bool $blocking = false)
+    {
+        if ($this->outputDisabled) {
+            throw new LogicException('Output has been disabled.');
+        }
+
+        $this->requireProcessIsStarted($caller);
+
+        $this->updateStatus($blocking);
+    }
+
+    /**
+     * Validates and returns the filtered timeout.
+     *
+     * @throws InvalidArgumentException if the given timeout is a negative number
+     */
+    private function validateTimeout(?float $timeout): ?float
+    {
+        $timeout = (float) $timeout;
+
+        if (0.0 === $timeout) {
+            $timeout = null;
+        } elseif ($timeout < 0) {
+            throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+        }
+
+        return $timeout;
+    }
+
+    /**
+     * Reads pipes, executes callback.
+     *
+     * @param bool $blocking Whether to use blocking calls or not
+     * @param bool $close    Whether to close file handles or not
+     */
+    private function readPipes(bool $blocking, bool $close)
+    {
+        $result = $this->processPipes->readAndWrite($blocking, $close);
+
+        $callback = $this->callback;
+        foreach ($result as $type => $data) {
+            if (3 !== $type) {
+                $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
+            } elseif (!isset($this->fallbackStatus['signaled'])) {
+                $this->fallbackStatus['exitcode'] = (int) $data;
+            }
+        }
+    }
+
+    /**
+     * Closes process resource, closes file handles, sets the exitcode.
+     *
+     * @return int The exitcode
+     */
+    private function close(): int
+    {
+        $this->processPipes->close();
+        if (\is_resource($this->process)) {
+            proc_close($this->process);
+        }
+        $this->exitcode = $this->processInformation['exitcode'];
+        $this->status = self::STATUS_TERMINATED;
+
+        if (-1 === $this->exitcode) {
+            if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+                // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+                $this->exitcode = 128 + $this->processInformation['termsig'];
+            } elseif ($this->isSigchildEnabled()) {
+                $this->processInformation['signaled'] = true;
+                $this->processInformation['termsig'] = -1;
+            }
+        }
+
+        // Free memory from self-reference callback created by buildCallback
+        // Doing so in other contexts like __destruct or by garbage collector is ineffective
+        // Now pipes are closed, so the callback is no longer necessary
+        $this->callback = null;
+
+        return $this->exitcode;
+    }
+
+    /**
+     * Resets data related to the latest run of the process.
+     */
+    private function resetProcessData()
+    {
+        $this->starttime = null;
+        $this->callback = null;
+        $this->exitcode = null;
+        $this->fallbackStatus = [];
+        $this->processInformation = null;
+        $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
+        $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
+        $this->process = null;
+        $this->latestSignal = null;
+        $this->status = self::STATUS_READY;
+        $this->incrementalOutputOffset = 0;
+        $this->incrementalErrorOutputOffset = 0;
+    }
+
+    /**
+     * Sends a POSIX signal to the process.
+     *
+     * @param int  $signal         A valid POSIX signal (see https://php.net/pcntl.constants)
+     * @param bool $throwException Whether to throw exception in case signal failed
+     *
+     * @return bool True if the signal was sent successfully, false otherwise
+     *
+     * @throws LogicException   In case the process is not running
+     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+     * @throws RuntimeException In case of failure
+     */
+    private function doSignal(int $signal, bool $throwException): bool
+    {
+        if (null === $pid = $this->getPid()) {
+            if ($throwException) {
+                throw new LogicException('Can not send signal on a non running process.');
+            }
+
+            return false;
+        }
+
+        if ('\\' === \DIRECTORY_SEPARATOR) {
+            exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
+            if ($exitCode && $this->isRunning()) {
+                if ($throwException) {
+                    throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+                }
+
+                return false;
+            }
+        } else {
+            if (!$this->isSigchildEnabled()) {
+                $ok = @proc_terminate($this->process, $signal);
+            } elseif (\function_exists('posix_kill')) {
+                $ok = @posix_kill($pid, $signal);
+            } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
+                $ok = false === fgets($pipes[2]);
+            }
+            if (!$ok) {
+                if ($throwException) {
+                    throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
+                }
+
+                return false;
+            }
+        }
+
+        $this->latestSignal = $signal;
+        $this->fallbackStatus['signaled'] = true;
+        $this->fallbackStatus['exitcode'] = -1;
+        $this->fallbackStatus['termsig'] = $this->latestSignal;
+
+        return true;
+    }
+
+    private function prepareWindowsCommandLine(string $cmd, array &$env): string
+    {
+        $uid = uniqid('', true);
+        $varCount = 0;
+        $varCache = [];
+        $cmd = preg_replace_callback(
+            '/"(?:(
+                [^"%!^]*+
+                (?:
+                    (?: !LF! | "(?:\^[%!^])?+" )
+                    [^"%!^]*+
+                )++
+            ) | [^"]*+ )"/x',
+            function ($m) use (&$env, &$varCache, &$varCount, $uid) {
+                if (!isset($m[1])) {
+                    return $m[0];
+                }
+                if (isset($varCache[$m[0]])) {
+                    return $varCache[$m[0]];
+                }
+                if (false !== strpos($value = $m[1], "\0")) {
+                    $value = str_replace("\0", '?', $value);
+                }
+                if (false === strpbrk($value, "\"%!\n")) {
+                    return '"'.$value.'"';
+                }
+
+                $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
+                $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
+                $var = $uid.++$varCount;
+
+                $env[$var] = $value;
+
+                return $varCache[$m[0]] = '!'.$var.'!';
+            },
+            $cmd
+        );
+
+        $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
+        foreach ($this->processPipes->getFiles() as $offset => $filename) {
+            $cmd .= ' '.$offset.'>"'.$filename.'"';
+        }
+
+        return $cmd;
+    }
+
+    /**
+     * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+     *
+     * @throws LogicException if the process has not run
+     */
+    private function requireProcessIsStarted(string $functionName)
+    {
+        if (!$this->isStarted()) {
+            throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
+        }
+    }
+
+    /**
+     * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
+     *
+     * @throws LogicException if the process is not yet terminated
+     */
+    private function requireProcessIsTerminated(string $functionName)
+    {
+        if (!$this->isTerminated()) {
+            throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
+        }
+    }
+
+    /**
+     * Escapes a string to be used as a shell argument.
+     */
+    private function escapeArgument(?string $argument): string
+    {
+        if ('' === $argument || null === $argument) {
+            return '""';
+        }
+        if ('\\' !== \DIRECTORY_SEPARATOR) {
+            return "'".str_replace("'", "'\\''", $argument)."'";
+        }
+        if (false !== strpos($argument, "\0")) {
+            $argument = str_replace("\0", '?', $argument);
+        }
+        if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
+            return $argument;
+        }
+        $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
+
+        return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
+    }
+
+    private function replacePlaceholders(string $commandline, array $env)
+    {
+        return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
+            if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
+                throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
+            }
+
+            return $this->escapeArgument($env[$matches[1]]);
+        }, $commandline);
+    }
+
+    private function getDefaultEnv(): array
+    {
+        $env = [];
+
+        foreach ($_SERVER as $k => $v) {
+            if (\is_string($v) && false !== $v = getenv($k)) {
+                $env[$k] = $v;
+            }
+        }
+
+        foreach ($_ENV as $k => $v) {
+            if (\is_string($v)) {
+                $env[$k] = $v;
+            }
+        }
+
+        return $env;
+    }
+}

+ 69 - 0
api/vendor/symfony/process/ProcessUtils.php

@@ -0,0 +1,69 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň <martin.hason@gmail.com>
+ */
+class ProcessUtils
+{
+    /**
+     * This class should not be instantiated.
+     */
+    private function __construct()
+    {
+    }
+
+    /**
+     * Validates and normalizes a Process input.
+     *
+     * @param string $caller The name of method call that validates the input
+     * @param mixed  $input  The input to validate
+     *
+     * @return mixed The validated input
+     *
+     * @throws InvalidArgumentException In case the input is not valid
+     */
+    public static function validateInput(string $caller, $input)
+    {
+        if (null !== $input) {
+            if (\is_resource($input)) {
+                return $input;
+            }
+            if (\is_string($input)) {
+                return $input;
+            }
+            if (is_scalar($input)) {
+                return (string) $input;
+            }
+            if ($input instanceof Process) {
+                return $input->getIterator($input::ITER_SKIP_ERR);
+            }
+            if ($input instanceof \Iterator) {
+                return $input;
+            }
+            if ($input instanceof \Traversable) {
+                return new \IteratorIterator($input);
+            }
+
+            throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
+        }
+
+        return $input;
+    }
+}

+ 13 - 0
api/vendor/symfony/process/README.md

@@ -0,0 +1,13 @@
+Process Component
+=================
+
+The Process component executes commands in sub-processes.
+
+Resources
+---------
+
+  * [Documentation](https://symfony.com/doc/current/components/process.html)
+  * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+  * [Report issues](https://github.com/symfony/symfony/issues) and
+    [send Pull Requests](https://github.com/symfony/symfony/pulls)
+    in the [main Symfony repository](https://github.com/symfony/symfony)

+ 29 - 0
api/vendor/symfony/process/composer.json

@@ -0,0 +1,29 @@
+{
+    "name": "symfony/process",
+    "type": "library",
+    "description": "Executes commands in sub-processes",
+    "keywords": [],
+    "homepage": "https://symfony.com",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Fabien Potencier",
+            "email": "fabien@symfony.com"
+        },
+        {
+            "name": "Symfony Community",
+            "homepage": "https://symfony.com/contributors"
+        }
+    ],
+    "require": {
+        "php": ">=7.2.5",
+        "symfony/polyfill-php80": "^1.15"
+    },
+    "autoload": {
+        "psr-4": { "Symfony\\Component\\Process\\": "" },
+        "exclude-from-classmap": [
+            "/Tests/"
+        ]
+    },
+    "minimum-stability": "dev"
+}