Explorar o código

Initial Commit

Anand Ayyappan hai 1 ano
achega
a3c3a6f69b
Modificáronse 100 ficheiros con 15121 adicións e 0 borrados
  1. 8 0
      .gitignore
  2. 52 0
      api/v1/addProvider/index.php
  3. 19 0
      api/v1/addService/index.php
  4. 6 0
      api/v1/composer.json
  5. 455 0
      api/v1/composer.lock
  6. 12 0
      api/v1/getListing/index.php
  7. 4 0
      api/v1/index.php
  8. 19 0
      api/v1/removeService/index.php
  9. 14 0
      api/v1/updateLocation/index.php
  10. 25 0
      api/v1/vendor/autoload.php
  11. 585 0
      api/v1/vendor/composer/ClassLoader.php
  12. 352 0
      api/v1/vendor/composer/InstalledVersions.php
  13. 21 0
      api/v1/vendor/composer/LICENSE
  14. 17 0
      api/v1/vendor/composer/autoload_classmap.php
  15. 13 0
      api/v1/vendor/composer/autoload_files.php
  16. 9 0
      api/v1/vendor/composer/autoload_namespaces.php
  17. 15 0
      api/v1/vendor/composer/autoload_psr4.php
  18. 50 0
      api/v1/vendor/composer/autoload_real.php
  19. 84 0
      api/v1/vendor/composer/autoload_static.php
  20. 460 0
      api/v1/vendor/composer/installed.json
  21. 77 0
      api/v1/vendor/composer/installed.php
  22. 26 0
      api/v1/vendor/composer/platform_check.php
  23. 156 0
      api/v1/vendor/firebase/php-jwt/CHANGELOG.md
  24. 30 0
      api/v1/vendor/firebase/php-jwt/LICENSE
  25. 424 0
      api/v1/vendor/firebase/php-jwt/README.md
  26. 42 0
      api/v1/vendor/firebase/php-jwt/composer.json
  27. 7 0
      api/v1/vendor/firebase/php-jwt/src/BeforeValidException.php
  28. 268 0
      api/v1/vendor/firebase/php-jwt/src/CachedKeySet.php
  29. 7 0
      api/v1/vendor/firebase/php-jwt/src/ExpiredException.php
  30. 349 0
      api/v1/vendor/firebase/php-jwt/src/JWK.php
  31. 662 0
      api/v1/vendor/firebase/php-jwt/src/JWT.php
  32. 64 0
      api/v1/vendor/firebase/php-jwt/src/Key.php
  33. 7 0
      api/v1/vendor/firebase/php-jwt/src/SignatureInvalidException.php
  34. 76 0
      api/v1/vendor/jean85/pretty-package-versions/.github/workflows/tests.yaml
  35. 118 0
      api/v1/vendor/jean85/pretty-package-versions/.php_cs.dist
  36. 21 0
      api/v1/vendor/jean85/pretty-package-versions/LICENSE
  37. 19 0
      api/v1/vendor/jean85/pretty-package-versions/Makefile
  38. 1 0
      api/v1/vendor/jean85/pretty-package-versions/codecov.yml
  39. 50 0
      api/v1/vendor/jean85/pretty-package-versions/composer.json
  40. 5 0
      api/v1/vendor/jean85/pretty-package-versions/phpstan.neon
  41. 16 0
      api/v1/vendor/jean85/pretty-package-versions/psalm.xml
  42. 13 0
      api/v1/vendor/jean85/pretty-package-versions/src/Exception/ProvidedPackageException.php
  43. 13 0
      api/v1/vendor/jean85/pretty-package-versions/src/Exception/ReplacedPackageException.php
  44. 10 0
      api/v1/vendor/jean85/pretty-package-versions/src/Exception/VersionMissingExceptionInterface.php
  45. 77 0
      api/v1/vendor/jean85/pretty-package-versions/src/PrettyVersions.php
  46. 101 0
      api/v1/vendor/jean85/pretty-package-versions/src/Version.php
  47. 202 0
      api/v1/vendor/mongodb/mongodb/LICENSE
  48. 47 0
      api/v1/vendor/mongodb/mongodb/composer.json
  49. 33 0
      api/v1/vendor/mongodb/mongodb/rector.php
  50. 188 0
      api/v1/vendor/mongodb/mongodb/src/BulkWriteResult.php
  51. 276 0
      api/v1/vendor/mongodb/mongodb/src/ChangeStream.php
  52. 423 0
      api/v1/vendor/mongodb/mongodb/src/Client.php
  53. 1160 0
      api/v1/vendor/mongodb/mongodb/src/Collection.php
  54. 154 0
      api/v1/vendor/mongodb/mongodb/src/Command/ListCollections.php
  55. 157 0
      api/v1/vendor/mongodb/mongodb/src/Command/ListDatabases.php
  56. 646 0
      api/v1/vendor/mongodb/mongodb/src/Database.php
  57. 70 0
      api/v1/vendor/mongodb/mongodb/src/DeleteResult.php
  58. 47 0
      api/v1/vendor/mongodb/mongodb/src/Exception/BadMethodCallException.php
  59. 56 0
      api/v1/vendor/mongodb/mongodb/src/Exception/CreateEncryptedCollectionException.php
  60. 24 0
      api/v1/vendor/mongodb/mongodb/src/Exception/Exception.php
  61. 62 0
      api/v1/vendor/mongodb/mongodb/src/Exception/InvalidArgumentException.php
  62. 45 0
      api/v1/vendor/mongodb/mongodb/src/Exception/ResumeTokenException.php
  63. 24 0
      api/v1/vendor/mongodb/mongodb/src/Exception/RuntimeException.php
  64. 24 0
      api/v1/vendor/mongodb/mongodb/src/Exception/UnexpectedValueException.php
  65. 128 0
      api/v1/vendor/mongodb/mongodb/src/Exception/UnsupportedException.php
  66. 736 0
      api/v1/vendor/mongodb/mongodb/src/GridFS/Bucket.php
  67. 367 0
      api/v1/vendor/mongodb/mongodb/src/GridFS/CollectionWrapper.php
  68. 68 0
      api/v1/vendor/mongodb/mongodb/src/GridFS/Exception/CorruptFileException.php
  69. 54 0
      api/v1/vendor/mongodb/mongodb/src/GridFS/Exception/FileNotFoundException.php
  70. 47 0
      api/v1/vendor/mongodb/mongodb/src/GridFS/Exception/StreamException.php
  71. 323 0
      api/v1/vendor/mongodb/mongodb/src/GridFS/ReadableStream.php
  72. 326 0
      api/v1/vendor/mongodb/mongodb/src/GridFS/StreamWrapper.php
  73. 305 0
      api/v1/vendor/mongodb/mongodb/src/GridFS/WritableStream.php
  74. 90 0
      api/v1/vendor/mongodb/mongodb/src/InsertManyResult.php
  75. 93 0
      api/v1/vendor/mongodb/mongodb/src/InsertOneResult.php
  76. 108 0
      api/v1/vendor/mongodb/mongodb/src/MapReduceResult.php
  77. 103 0
      api/v1/vendor/mongodb/mongodb/src/Model/BSONArray.php
  78. 107 0
      api/v1/vendor/mongodb/mongodb/src/Model/BSONDocument.php
  79. 156 0
      api/v1/vendor/mongodb/mongodb/src/Model/BSONIterator.php
  80. 164 0
      api/v1/vendor/mongodb/mongodb/src/Model/CachingIterator.php
  81. 82 0
      api/v1/vendor/mongodb/mongodb/src/Model/CallbackIterator.php
  82. 304 0
      api/v1/vendor/mongodb/mongodb/src/Model/ChangeStreamIterator.php
  83. 200 0
      api/v1/vendor/mongodb/mongodb/src/Model/CollectionInfo.php
  84. 62 0
      api/v1/vendor/mongodb/mongodb/src/Model/CollectionInfoCommandIterator.php
  85. 39 0
      api/v1/vendor/mongodb/mongodb/src/Model/CollectionInfoIterator.php
  86. 142 0
      api/v1/vendor/mongodb/mongodb/src/Model/DatabaseInfo.php
  87. 39 0
      api/v1/vendor/mongodb/mongodb/src/Model/DatabaseInfoIterator.php
  88. 95 0
      api/v1/vendor/mongodb/mongodb/src/Model/DatabaseInfoLegacyIterator.php
  89. 241 0
      api/v1/vendor/mongodb/mongodb/src/Model/IndexInfo.php
  90. 39 0
      api/v1/vendor/mongodb/mongodb/src/Model/IndexInfoIterator.php
  91. 66 0
      api/v1/vendor/mongodb/mongodb/src/Model/IndexInfoIteratorIterator.php
  92. 115 0
      api/v1/vendor/mongodb/mongodb/src/Model/IndexInput.php
  93. 392 0
      api/v1/vendor/mongodb/mongodb/src/Operation/Aggregate.php
  94. 396 0
      api/v1/vendor/mongodb/mongodb/src/Operation/BulkWrite.php
  95. 236 0
      api/v1/vendor/mongodb/mongodb/src/Operation/Count.php
  96. 169 0
      api/v1/vendor/mongodb/mongodb/src/Operation/CountDocuments.php
  97. 311 0
      api/v1/vendor/mongodb/mongodb/src/Operation/CreateCollection.php
  98. 193 0
      api/v1/vendor/mongodb/mongodb/src/Operation/CreateEncryptedCollection.php
  99. 202 0
      api/v1/vendor/mongodb/mongodb/src/Operation/CreateIndexes.php
  100. 126 0
      api/v1/vendor/mongodb/mongodb/src/Operation/DatabaseCommand.php

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+# ---> Composer
+composer.phar
+vendor/
+api/v1/vendor/
+mdb/vendor/
+# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
+# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
+# composer.lock

+ 52 - 0
api/v1/addProvider/index.php

@@ -0,0 +1,52 @@
+<?php
+require_once("../vendor/autoload.php");
+use MongoDB\Client;
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+
+$client = new Client('mongodb://kpadmin:AnandAyyappan1981@127.0.0.1:27017/?authSource=kpmaster');
+$collection = $client->selectCollection('kpmaster', 'kpu');
+try { 
+$insertOneResult =  $collection->insertOne([
+    'fn' => 'Anand 1',
+  	'mn' => '918606033444',
+    'em' => 'a2n1and@gami11l.com',
+  	'dob'=>'21/12/1981',
+  	'srvs' => ['One','Two'],
+    'location' => [
+    'coordinates'=> [76.76824,8.736739],
+    'type' => 'Point'
+     ],
+  	'ps' => md5('AnandAyyappan')
+]);
+
+if($insertOneResult->getInsertedCount() == 1){
+  $id= get_object_vars($insertOneResult->getInsertedId());
+  //print_r($id);
+  $key = '97eda3db6f898bfb1f821178d1c27064';
+  $payload = [
+      'iss' => 'http://kittipoyi.com',
+      'aud' => 'http://kittipoyi.com',
+      'iat' => time(),
+      'nbf' => time(),
+      'kpid' =>  $id['oid']
+  ];
+  $jwt = JWT::encode($payload, $key, 'HS256');  
+ $message['status'] = "success";
+ $message['token'] = $jwt;
+}else{
+ $message['status'] = "error";
+ $message['message'] = "Provider Addition Failed";
+}
+}catch(\Exception $e) {
+  		//echo $e->getMessage();
+  		if (str_contains( $e->getMessage(), 'duplicate key error collection: kpmaster.kpu index: em_text dup key')) {
+            $message['status'] = "error";
+            $message['message'] =  "User alerady exists for the given email id";
+        }else{
+             $message['status'] = "error";
+             $message['message'] =  "Error Occured While adding user contact Administrator";
+        }
+}
+echo json_encode($message);
+?>

+ 19 - 0
api/v1/addService/index.php

@@ -0,0 +1,19 @@
+<?php
+require_once("../../../mdb/vendor/autoload.php");
+use MongoDB\BSON\ObjectId;
+use MongoDB\Client;
+
+// Requires the MongoDB PHP Driver
+// https://www.mongodb.com/docs/drivers/php/
+
+$client = new Client('mongodb://kpadmin:AnandAyyappan1981@127.0.0.1:27017/?authSource=kpmaster');
+$collection = $client->selectCollection('kpmaster', 'kpu');
+//$cursor = $collection->aggregate([['$geoNear' => ['near' => ['type' => 'Point', 'coordinates' => [76.93, 8.52]], 'spherical' => true, 'query' => ['srvs' => ['$all' => ['Painting']]], 'distanceField' => 'Dis', 'maxDistance' => 2000, 'includeLocs' => 'loc']], ['$project' => ['ID'=>'$oid', 'Name' => '$fn', 'Distance' => '$Dis', 'Location' => '$loc']]]);
+$updateResult = $collection->updateOne(
+   [ '_id' => new ObjectId('64b0f61b8b3742ad72109e80') ],
+   [ '$push' => [ 'srvs' => 'Running' ]]
+);
+
+printf("Matched %d document(s)\n", $updateResult->getMatchedCount());
+printf("Modified %d document(s)\n", $updateResult->getModifiedCount());
+?>

+ 6 - 0
api/v1/composer.json

@@ -0,0 +1,6 @@
+{
+    "require": {
+        "mongodb/mongodb": "^1.16",
+        "firebase/php-jwt": "^6.8"
+    }
+}

+ 455 - 0
api/v1/composer.lock

@@ -0,0 +1,455 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "59b82b3838dd243a812e99a36ade26c8",
+    "packages": [
+        {
+            "name": "firebase/php-jwt",
+            "version": "v6.8.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/firebase/php-jwt.git",
+                "reference": "5dbc8959427416b8ee09a100d7a8588c00fb2e26"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/5dbc8959427416b8ee09a100d7a8588c00fb2e26",
+                "reference": "5dbc8959427416b8ee09a100d7a8588c00fb2e26",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4||^8.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^6.5||^7.4",
+                "phpspec/prophecy-phpunit": "^2.0",
+                "phpunit/phpunit": "^9.5",
+                "psr/cache": "^1.0||^2.0",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0"
+            },
+            "suggest": {
+                "ext-sodium": "Support EdDSA (Ed25519) signatures",
+                "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Firebase\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Neuman Vong",
+                    "email": "neuman+pear@twilio.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Anant Narayanan",
+                    "email": "anant@php.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+            "homepage": "https://github.com/firebase/php-jwt",
+            "keywords": [
+                "jwt",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/firebase/php-jwt/issues",
+                "source": "https://github.com/firebase/php-jwt/tree/v6.8.1"
+            },
+            "time": "2023-07-14T18:33:00+00:00"
+        },
+        {
+            "name": "jean85/pretty-package-versions",
+            "version": "2.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Jean85/pretty-package-versions.git",
+                "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af",
+                "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af",
+                "shasum": ""
+            },
+            "require": {
+                "composer-runtime-api": "^2.0.0",
+                "php": "^7.1|^8.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.17",
+                "jean85/composer-provided-replaced-stub-package": "^1.0",
+                "phpstan/phpstan": "^0.12.66",
+                "phpunit/phpunit": "^7.5|^8.5|^9.4",
+                "vimeo/psalm": "^4.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Jean85\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Alessandro Lai",
+                    "email": "alessandro.lai85@gmail.com"
+                }
+            ],
+            "description": "A library to get pretty versions strings of installed dependencies",
+            "keywords": [
+                "composer",
+                "package",
+                "release",
+                "versions"
+            ],
+            "support": {
+                "issues": "https://github.com/Jean85/pretty-package-versions/issues",
+                "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5"
+            },
+            "time": "2021-10-08T21:21:46+00:00"
+        },
+        {
+            "name": "mongodb/mongodb",
+            "version": "1.16.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mongodb/mongo-php-library.git",
+                "reference": "d4cdf057a67cb99a32db8984a16959bfa7ca7eb5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/d4cdf057a67cb99a32db8984a16959bfa7ca7eb5",
+                "reference": "d4cdf057a67cb99a32db8984a16959bfa7ca7eb5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-hash": "*",
+                "ext-json": "*",
+                "ext-mongodb": "^1.16.0",
+                "jean85/pretty-package-versions": "^2.0.1",
+                "php": "^7.2 || ^8.0",
+                "symfony/polyfill-php73": "^1.27",
+                "symfony/polyfill-php80": "^1.27",
+                "symfony/polyfill-php81": "^1.27"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^11.1",
+                "rector/rector": "^0.16.0",
+                "squizlabs/php_codesniffer": "^3.7",
+                "symfony/phpunit-bridge": "^5.2",
+                "vimeo/psalm": "^4.28"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.16.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions.php"
+                ],
+                "psr-4": {
+                    "MongoDB\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Andreas Braun",
+                    "email": "andreas.braun@mongodb.com"
+                },
+                {
+                    "name": "Jeremy Mikola",
+                    "email": "jmikola@gmail.com"
+                }
+            ],
+            "description": "MongoDB driver library",
+            "homepage": "https://jira.mongodb.org/browse/PHPLIB",
+            "keywords": [
+                "database",
+                "driver",
+                "mongodb",
+                "persistence"
+            ],
+            "support": {
+                "issues": "https://github.com/mongodb/mongo-php-library/issues",
+                "source": "https://github.com/mongodb/mongo-php-library/tree/1.16.0"
+            },
+            "time": "2023-06-22T11:04:04+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php73",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9",
+                "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php81",
+            "version": "v1.27.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php81.git",
+                "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a",
+                "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php81\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "time": "2022-11-03T14:55:06+00:00"
+        }
+    ],
+    "packages-dev": [],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": [],
+    "platform-dev": [],
+    "plugin-api-version": "2.3.0"
+}

+ 12 - 0
api/v1/getListing/index.php

@@ -0,0 +1,12 @@
+<?php
+require_once("../vendor/autoload.php");
+use MongoDB\Client;
+
+// Requires the MongoDB PHP Driver
+// https://www.mongodb.com/docs/drivers/php/
+
+$client = new Client('mongodb://kpadmin:AnandAyyappan1981@127.0.0.1:27017/?authSource=kpmaster');
+$collection = $client->selectCollection('kpmaster', 'kpu');
+$cursor = $collection->aggregate([['$geoNear' => ['near' => ['type' => 'Point', 'coordinates' => [76.768, 8.736]], 'spherical' => true, 'query' => ['srvs' => ['$all' => ['One']]], 'distanceField' => 'Dis', 'maxDistance' => 2000, 'includeLocs' => 'loc']], ['$project' => ['ID'=>'$oid', 'Name' => '$fn', 'Distance' => '$Dis', 'Location' => '$loc']]]);
+echo json_encode(iterator_to_array($cursor));
+?>

+ 4 - 0
api/v1/index.php

@@ -0,0 +1,4 @@
+<?php
+if($_GET['show'] == 1){
+  phpinfo();
+}?>

+ 19 - 0
api/v1/removeService/index.php

@@ -0,0 +1,19 @@
+<?php
+require_once("../../../mdb/vendor/autoload.php");
+use MongoDB\BSON\ObjectId;
+use MongoDB\Client;
+
+// Requires the MongoDB PHP Driver
+// https://www.mongodb.com/docs/drivers/php/
+
+$client = new Client('mongodb://kpadmin:AnandAyyappan1981@127.0.0.1:27017/?authSource=kpmaster');
+$collection = $client->selectCollection('kpmaster', 'kpu');
+//$cursor = $collection->aggregate([['$geoNear' => ['near' => ['type' => 'Point', 'coordinates' => [76.93, 8.52]], 'spherical' => true, 'query' => ['srvs' => ['$all' => ['Painting']]], 'distanceField' => 'Dis', 'maxDistance' => 2000, 'includeLocs' => 'loc']], ['$project' => ['ID'=>'$oid', 'Name' => '$fn', 'Distance' => '$Dis', 'Location' => '$loc']]]);
+$updateResult = $collection->updateOne(
+   [ '_id' => new ObjectId('64b0f61b8b3742ad72109e80') ],
+   [ '$pull' => [ 'srvs' => 'Washing' ]]
+);
+
+printf("Matched %d document(s)\n", $updateResult->getMatchedCount());
+printf("Modified %d document(s)\n", $updateResult->getModifiedCount());
+?>

+ 14 - 0
api/v1/updateLocation/index.php

@@ -0,0 +1,14 @@
+<?php
+require_once("../../../mdb/vendor/autoload.php");
+use MongoDB\BSON\ObjectId;
+use MongoDB\Client;
+
+// Requires the MongoDB PHP Driver
+// https://www.mongodb.com/docs/drivers/php/
+
+$client = new Client('mongodb://kpadmin:AnandAyyappan1981@127.0.0.1:27017/?authSource=kpmaster');
+$collection = $client->selectCollection('kpmaster', 'kpu');
+$return = $collection->updateOne(['_id' => new ObjectId('64b0f61b8b3742ad72109e80')], ['$set' => ['location' => ['coordinates' => [76.7682501,8.73673601],'type'=> "Point"]]]);
+printf("Matched %d document(s)\n", $return->getMatchedCount());
+printf("Modified %d document(s)\n", $return->getModifiedCount());
+?>

+ 25 - 0
api/v1/vendor/autoload.php

@@ -0,0 +1,25 @@
+<?php
+
+// autoload.php @generated by Composer
+
+if (PHP_VERSION_ID < 50600) {
+    if (!headers_sent()) {
+        header('HTTP/1.1 500 Internal Server Error');
+    }
+    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
+    if (!ini_get('display_errors')) {
+        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+            fwrite(STDERR, $err);
+        } elseif (!headers_sent()) {
+            echo $err;
+        }
+    }
+    trigger_error(
+        $err,
+        E_USER_ERROR
+    );
+}
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInita389fa583f5429cf1e54fe348ade916a::getLoader();

+ 585 - 0
api/v1/vendor/composer/ClassLoader.php

@@ -0,0 +1,585 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    https://www.php-fig.org/psr/psr-0/
+ * @see    https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    /** @var \Closure(string):void */
+    private static $includeFile;
+
+    /** @var ?string */
+    private $vendorDir;
+
+    // PSR-4
+    /**
+     * @var array[]
+     * @psalm-var array<string, array<string, int>>
+     */
+    private $prefixLengthsPsr4 = array();
+    /**
+     * @var array[]
+     * @psalm-var array<string, array<int, string>>
+     */
+    private $prefixDirsPsr4 = array();
+    /**
+     * @var array[]
+     * @psalm-var array<string, string>
+     */
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    /**
+     * @var array[]
+     * @psalm-var array<string, array<string, string[]>>
+     */
+    private $prefixesPsr0 = array();
+    /**
+     * @var array[]
+     * @psalm-var array<string, string>
+     */
+    private $fallbackDirsPsr0 = array();
+
+    /** @var bool */
+    private $useIncludePath = false;
+
+    /**
+     * @var string[]
+     * @psalm-var array<string, string>
+     */
+    private $classMap = array();
+
+    /** @var bool */
+    private $classMapAuthoritative = false;
+
+    /**
+     * @var bool[]
+     * @psalm-var array<string, bool>
+     */
+    private $missingClasses = array();
+
+    /** @var ?string */
+    private $apcuPrefix;
+
+    /**
+     * @var self[]
+     */
+    private static $registeredLoaders = array();
+
+    /**
+     * @param ?string $vendorDir
+     */
+    public function __construct($vendorDir = null)
+    {
+        $this->vendorDir = $vendorDir;
+        self::initializeIncludeClosure();
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+        }
+
+        return array();
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return array<string, array<int, string>>
+     */
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return array<string, string>
+     */
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return array<string, string>
+     */
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    /**
+     * @return string[] Array of classname => path
+     * @psalm-return array<string, string>
+     */
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param string[] $classMap Class to filename map
+     * @psalm-param array<string, string> $classMap
+     *
+     * @return void
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string          $prefix  The prefix
+     * @param string[]|string $paths   The PSR-0 root directories
+     * @param bool            $prepend Whether to prepend the directories
+     *
+     * @return void
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string          $prefix  The prefix/namespace, with trailing '\\'
+     * @param string[]|string $paths   The PSR-4 base directories
+     * @param bool            $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return void
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string          $prefix The prefix
+     * @param string[]|string $paths  The PSR-0 base directories
+     *
+     * @return void
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string          $prefix The prefix/namespace, with trailing '\\'
+     * @param string[]|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return void
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     *
+     * @return void
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     *
+     * @return void
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     *
+     * @return void
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     *
+     * @return void
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+        if (null === $this->vendorDir) {
+            return;
+        }
+
+        if ($prepend) {
+            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+        } else {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+            self::$registeredLoaders[$this->vendorDir] = $this;
+        }
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     *
+     * @return void
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+
+        if (null !== $this->vendorDir) {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+        }
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return true|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            $includeFile = self::$includeFile;
+            $includeFile($file);
+
+            return true;
+        }
+
+        return null;
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    /**
+     * Returns the currently registered loaders indexed by their corresponding vendor directories.
+     *
+     * @return self[]
+     */
+    public static function getRegisteredLoaders()
+    {
+        return self::$registeredLoaders;
+    }
+
+    /**
+     * @param  string       $class
+     * @param  string       $ext
+     * @return string|false
+     */
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath . '\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        if (file_exists($file = $dir . $pathEnd)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+
+    /**
+     * @return void
+     */
+    private static function initializeIncludeClosure()
+    {
+        if (self::$includeFile !== null) {
+            return;
+        }
+
+        /**
+         * Scope isolated include.
+         *
+         * Prevents access to $this/self from included files.
+         *
+         * @param  string $file
+         * @return void
+         */
+        self::$includeFile = \Closure::bind(static function($file) {
+            include $file;
+        }, null, null);
+    }
+}

+ 352 - 0
api/v1/vendor/composer/InstalledVersions.php

@@ -0,0 +1,352 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+    /**
+     * @var mixed[]|null
+     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
+     */
+    private static $installed;
+
+    /**
+     * @var bool|null
+     */
+    private static $canGetVendors;
+
+    /**
+     * @var array[]
+     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static $installedByVendor = array();
+
+    /**
+     * Returns a list of all package names which are present, either by being installed, replaced or provided
+     *
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackages()
+    {
+        $packages = array();
+        foreach (self::getInstalled() as $installed) {
+            $packages[] = array_keys($installed['versions']);
+        }
+
+        if (1 === \count($packages)) {
+            return $packages[0];
+        }
+
+        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+    }
+
+    /**
+     * Returns a list of all package names with a specific type e.g. 'library'
+     *
+     * @param  string   $type
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackagesByType($type)
+    {
+        $packagesByType = array();
+
+        foreach (self::getInstalled() as $installed) {
+            foreach ($installed['versions'] as $name => $package) {
+                if (isset($package['type']) && $package['type'] === $type) {
+                    $packagesByType[] = $name;
+                }
+            }
+        }
+
+        return $packagesByType;
+    }
+
+    /**
+     * Checks whether the given package is installed
+     *
+     * This also returns true if the package name is provided or replaced by another package
+     *
+     * @param  string $packageName
+     * @param  bool   $includeDevRequirements
+     * @return bool
+     */
+    public static function isInstalled($packageName, $includeDevRequirements = true)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (isset($installed['versions'][$packageName])) {
+                return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks whether the given package satisfies a version constraint
+     *
+     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+     *
+     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+     *
+     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
+     * @param  string        $packageName
+     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+     * @return bool
+     */
+    public static function satisfies(VersionParser $parser, $packageName, $constraint)
+    {
+        $constraint = $parser->parseConstraints($constraint);
+        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+        return $provided->matches($constraint);
+    }
+
+    /**
+     * Returns a version constraint representing all the range(s) which are installed for a given package
+     *
+     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+     * whether a given version of a package is installed, and not just whether it exists
+     *
+     * @param  string $packageName
+     * @return string Version constraint usable with composer/semver
+     */
+    public static function getVersionRanges($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            $ranges = array();
+            if (isset($installed['versions'][$packageName]['pretty_version'])) {
+                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+            }
+            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+            }
+            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+            }
+            if (array_key_exists('provided', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+            }
+
+            return implode(' || ', $ranges);
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getPrettyVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['pretty_version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+     */
+    public static function getReference($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['reference'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['reference'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+     */
+    public static function getInstallPath($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @return array
+     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+     */
+    public static function getRootPackage()
+    {
+        $installed = self::getInstalled();
+
+        return $installed[0]['root'];
+    }
+
+    /**
+     * Returns the raw installed.php data for custom implementations
+     *
+     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+     * @return array[]
+     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
+     */
+    public static function getRawData()
+    {
+        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = include __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        return self::$installed;
+    }
+
+    /**
+     * Returns the raw data of all installed.php which are currently loaded for custom implementations
+     *
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    public static function getAllRawData()
+    {
+        return self::getInstalled();
+    }
+
+    /**
+     * Lets you reload the static array from another file
+     *
+     * This is only useful for complex integrations in which a project needs to use
+     * this class but then also needs to execute another project's autoloader in process,
+     * and wants to ensure both projects have access to their version of installed.php.
+     *
+     * A typical case would be PHPUnit, where it would need to make sure it reads all
+     * the data it needs from this class, then call reload() with
+     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+     * the project in which it runs can then also use this class safely, without
+     * interference between PHPUnit's dependencies and the project's dependencies.
+     *
+     * @param  array[] $data A vendor/composer/installed.php data set
+     * @return void
+     *
+     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
+     */
+    public static function reload($data)
+    {
+        self::$installed = $data;
+        self::$installedByVendor = array();
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static function getInstalled()
+    {
+        if (null === self::$canGetVendors) {
+            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+        }
+
+        $installed = array();
+
+        if (self::$canGetVendors) {
+            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+                if (isset(self::$installedByVendor[$vendorDir])) {
+                    $installed[] = self::$installedByVendor[$vendorDir];
+                } elseif (is_file($vendorDir.'/composer/installed.php')) {
+                    $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+                        self::$installed = $installed[count($installed) - 1];
+                    }
+                }
+            }
+        }
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = require __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+        $installed[] = self::$installed;
+
+        return $installed;
+    }
+}

+ 21 - 0
api/v1/vendor/composer/LICENSE

@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+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.
+

+ 17 - 0
api/v1/vendor/composer/autoload_classmap.php

@@ -0,0 +1,17 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
+    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+    'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
+    'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
+    'ReturnTypeWillChange' => $vendorDir . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php',
+    'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+    'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+    'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+);

+ 13 - 0
api/v1/vendor/composer/autoload_files.php

@@ -0,0 +1,13 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
+    'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
+    '23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
+    '3a37ebac017bc098e9a86b35401e7a68' => $vendorDir . '/mongodb/mongodb/src/functions.php',
+);

+ 9 - 0
api/v1/vendor/composer/autoload_namespaces.php

@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+);

+ 15 - 0
api/v1/vendor/composer/autoload_psr4.php

@@ -0,0 +1,15 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'),
+    'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
+    'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
+    'MongoDB\\' => array($vendorDir . '/mongodb/mongodb/src'),
+    'Jean85\\' => array($vendorDir . '/jean85/pretty-package-versions/src'),
+    'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
+);

+ 50 - 0
api/v1/vendor/composer/autoload_real.php

@@ -0,0 +1,50 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInita389fa583f5429cf1e54fe348ade916a
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        require __DIR__ . '/platform_check.php';
+
+        spl_autoload_register(array('ComposerAutoloaderInita389fa583f5429cf1e54fe348ade916a', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+        spl_autoload_unregister(array('ComposerAutoloaderInita389fa583f5429cf1e54fe348ade916a', 'loadClassLoader'));
+
+        require __DIR__ . '/autoload_static.php';
+        call_user_func(\Composer\Autoload\ComposerStaticInita389fa583f5429cf1e54fe348ade916a::getInitializer($loader));
+
+        $loader->register(true);
+
+        $filesToLoad = \Composer\Autoload\ComposerStaticInita389fa583f5429cf1e54fe348ade916a::$files;
+        $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
+            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+
+                require $file;
+            }
+        }, null, null);
+        foreach ($filesToLoad as $fileIdentifier => $file) {
+            $requireFile($fileIdentifier, $file);
+        }
+
+        return $loader;
+    }
+}

+ 84 - 0
api/v1/vendor/composer/autoload_static.php

@@ -0,0 +1,84 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInita389fa583f5429cf1e54fe348ade916a
+{
+    public static $files = array (
+        '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
+        'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
+        '23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php',
+        '3a37ebac017bc098e9a86b35401e7a68' => __DIR__ . '/..' . '/mongodb/mongodb/src/functions.php',
+    );
+
+    public static $prefixLengthsPsr4 = array (
+        'S' => 
+        array (
+            'Symfony\\Polyfill\\Php81\\' => 23,
+            'Symfony\\Polyfill\\Php80\\' => 23,
+            'Symfony\\Polyfill\\Php73\\' => 23,
+        ),
+        'M' => 
+        array (
+            'MongoDB\\' => 8,
+        ),
+        'J' => 
+        array (
+            'Jean85\\' => 7,
+        ),
+        'F' => 
+        array (
+            'Firebase\\JWT\\' => 13,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Symfony\\Polyfill\\Php81\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-php81',
+        ),
+        'Symfony\\Polyfill\\Php80\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
+        ),
+        'Symfony\\Polyfill\\Php73\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
+        ),
+        'MongoDB\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/mongodb/mongodb/src',
+        ),
+        'Jean85\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/jean85/pretty-package-versions/src',
+        ),
+        'Firebase\\JWT\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/firebase/php-jwt/src',
+        ),
+    );
+
+    public static $classMap = array (
+        'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
+        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+        'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
+        'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
+        'ReturnTypeWillChange' => __DIR__ . '/..' . '/symfony/polyfill-php81/Resources/stubs/ReturnTypeWillChange.php',
+        'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+        'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+        'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInita389fa583f5429cf1e54fe348ade916a::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInita389fa583f5429cf1e54fe348ade916a::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInita389fa583f5429cf1e54fe348ade916a::$classMap;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 460 - 0
api/v1/vendor/composer/installed.json

@@ -0,0 +1,460 @@
+{
+    "packages": [
+        {
+            "name": "firebase/php-jwt",
+            "version": "v6.8.1",
+            "version_normalized": "6.8.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/firebase/php-jwt.git",
+                "reference": "5dbc8959427416b8ee09a100d7a8588c00fb2e26"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/5dbc8959427416b8ee09a100d7a8588c00fb2e26",
+                "reference": "5dbc8959427416b8ee09a100d7a8588c00fb2e26",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4||^8.0"
+            },
+            "require-dev": {
+                "guzzlehttp/guzzle": "^6.5||^7.4",
+                "phpspec/prophecy-phpunit": "^2.0",
+                "phpunit/phpunit": "^9.5",
+                "psr/cache": "^1.0||^2.0",
+                "psr/http-client": "^1.0",
+                "psr/http-factory": "^1.0"
+            },
+            "suggest": {
+                "ext-sodium": "Support EdDSA (Ed25519) signatures",
+                "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
+            },
+            "time": "2023-07-14T18:33:00+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Firebase\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Neuman Vong",
+                    "email": "neuman+pear@twilio.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Anant Narayanan",
+                    "email": "anant@php.net",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+            "homepage": "https://github.com/firebase/php-jwt",
+            "keywords": [
+                "jwt",
+                "php"
+            ],
+            "support": {
+                "issues": "https://github.com/firebase/php-jwt/issues",
+                "source": "https://github.com/firebase/php-jwt/tree/v6.8.1"
+            },
+            "install-path": "../firebase/php-jwt"
+        },
+        {
+            "name": "jean85/pretty-package-versions",
+            "version": "2.0.5",
+            "version_normalized": "2.0.5.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Jean85/pretty-package-versions.git",
+                "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af",
+                "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af",
+                "shasum": ""
+            },
+            "require": {
+                "composer-runtime-api": "^2.0.0",
+                "php": "^7.1|^8.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.17",
+                "jean85/composer-provided-replaced-stub-package": "^1.0",
+                "phpstan/phpstan": "^0.12.66",
+                "phpunit/phpunit": "^7.5|^8.5|^9.4",
+                "vimeo/psalm": "^4.3"
+            },
+            "time": "2021-10-08T21:21:46+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Jean85\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Alessandro Lai",
+                    "email": "alessandro.lai85@gmail.com"
+                }
+            ],
+            "description": "A library to get pretty versions strings of installed dependencies",
+            "keywords": [
+                "composer",
+                "package",
+                "release",
+                "versions"
+            ],
+            "support": {
+                "issues": "https://github.com/Jean85/pretty-package-versions/issues",
+                "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5"
+            },
+            "install-path": "../jean85/pretty-package-versions"
+        },
+        {
+            "name": "mongodb/mongodb",
+            "version": "1.16.0",
+            "version_normalized": "1.16.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mongodb/mongo-php-library.git",
+                "reference": "d4cdf057a67cb99a32db8984a16959bfa7ca7eb5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/d4cdf057a67cb99a32db8984a16959bfa7ca7eb5",
+                "reference": "d4cdf057a67cb99a32db8984a16959bfa7ca7eb5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-hash": "*",
+                "ext-json": "*",
+                "ext-mongodb": "^1.16.0",
+                "jean85/pretty-package-versions": "^2.0.1",
+                "php": "^7.2 || ^8.0",
+                "symfony/polyfill-php73": "^1.27",
+                "symfony/polyfill-php80": "^1.27",
+                "symfony/polyfill-php81": "^1.27"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^11.1",
+                "rector/rector": "^0.16.0",
+                "squizlabs/php_codesniffer": "^3.7",
+                "symfony/phpunit-bridge": "^5.2",
+                "vimeo/psalm": "^4.28"
+            },
+            "time": "2023-06-22T11:04:04+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.16.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "src/functions.php"
+                ],
+                "psr-4": {
+                    "MongoDB\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Andreas Braun",
+                    "email": "andreas.braun@mongodb.com"
+                },
+                {
+                    "name": "Jeremy Mikola",
+                    "email": "jmikola@gmail.com"
+                }
+            ],
+            "description": "MongoDB driver library",
+            "homepage": "https://jira.mongodb.org/browse/PHPLIB",
+            "keywords": [
+                "database",
+                "driver",
+                "mongodb",
+                "persistence"
+            ],
+            "support": {
+                "issues": "https://github.com/mongodb/mongo-php-library/issues",
+                "source": "https://github.com/mongodb/mongo-php-library/tree/1.16.0"
+            },
+            "install-path": "../mongodb/mongodb"
+        },
+        {
+            "name": "symfony/polyfill-php73",
+            "version": "v1.27.0",
+            "version_normalized": "1.27.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9",
+                "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "time": "2022-11-03T14:55:06+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "install-path": "../symfony/polyfill-php73"
+        },
+        {
+            "name": "symfony/polyfill-php80",
+            "version": "v1.27.0",
+            "version_normalized": "1.27.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php80.git",
+                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+                "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "time": "2022-11-03T14:55:06+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php80\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Ion Bazan",
+                    "email": "ion.bazan@gmail.com"
+                },
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "install-path": "../symfony/polyfill-php80"
+        },
+        {
+            "name": "symfony/polyfill-php81",
+            "version": "v1.27.0",
+            "version_normalized": "1.27.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php81.git",
+                "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a",
+                "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=7.1"
+            },
+            "time": "2022-11-03T14:55:06+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-main": "1.27-dev"
+                },
+                "thanks": {
+                    "name": "symfony/polyfill",
+                    "url": "https://github.com/symfony/polyfill"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php81\\": ""
+                },
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "support": {
+                "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0"
+            },
+            "funding": [
+                {
+                    "url": "https://symfony.com/sponsor",
+                    "type": "custom"
+                },
+                {
+                    "url": "https://github.com/fabpot",
+                    "type": "github"
+                },
+                {
+                    "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                    "type": "tidelift"
+                }
+            ],
+            "install-path": "../symfony/polyfill-php81"
+        }
+    ],
+    "dev": true,
+    "dev-package-names": []
+}

+ 77 - 0
api/v1/vendor/composer/installed.php

@@ -0,0 +1,77 @@
+<?php return array(
+    'root' => array(
+        'name' => '__root__',
+        'pretty_version' => '1.0.0+no-version-set',
+        'version' => '1.0.0.0',
+        'reference' => NULL,
+        'type' => 'library',
+        'install_path' => __DIR__ . '/../../',
+        'aliases' => array(),
+        'dev' => true,
+    ),
+    'versions' => array(
+        '__root__' => array(
+            'pretty_version' => '1.0.0+no-version-set',
+            'version' => '1.0.0.0',
+            'reference' => NULL,
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../../',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+        'firebase/php-jwt' => array(
+            'pretty_version' => 'v6.8.1',
+            'version' => '6.8.1.0',
+            'reference' => '5dbc8959427416b8ee09a100d7a8588c00fb2e26',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../firebase/php-jwt',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+        'jean85/pretty-package-versions' => array(
+            'pretty_version' => '2.0.5',
+            'version' => '2.0.5.0',
+            'reference' => 'ae547e455a3d8babd07b96966b17d7fd21d9c6af',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../jean85/pretty-package-versions',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+        'mongodb/mongodb' => array(
+            'pretty_version' => '1.16.0',
+            'version' => '1.16.0.0',
+            'reference' => 'd4cdf057a67cb99a32db8984a16959bfa7ca7eb5',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../mongodb/mongodb',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+        'symfony/polyfill-php73' => array(
+            'pretty_version' => 'v1.27.0',
+            'version' => '1.27.0.0',
+            'reference' => '9e8ecb5f92152187c4799efd3c96b78ccab18ff9',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/polyfill-php73',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+        'symfony/polyfill-php80' => array(
+            'pretty_version' => 'v1.27.0',
+            'version' => '1.27.0.0',
+            'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/polyfill-php80',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+        'symfony/polyfill-php81' => array(
+            'pretty_version' => 'v1.27.0',
+            'version' => '1.27.0.0',
+            'reference' => '707403074c8ea6e2edaf8794b0157a0bfa52157a',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/polyfill-php81',
+            'aliases' => array(),
+            'dev_requirement' => false,
+        ),
+    ),
+);

+ 26 - 0
api/v1/vendor/composer/platform_check.php

@@ -0,0 +1,26 @@
+<?php
+
+// platform_check.php @generated by Composer
+
+$issues = array();
+
+if (!(PHP_VERSION_ID >= 70400)) {
+    $issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+    if (!headers_sent()) {
+        header('HTTP/1.1 500 Internal Server Error');
+    }
+    if (!ini_get('display_errors')) {
+        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+        } elseif (!headers_sent()) {
+            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+        }
+    }
+    trigger_error(
+        'Composer detected issues in your platform: ' . implode(' ', $issues),
+        E_USER_ERROR
+    );
+}

+ 156 - 0
api/v1/vendor/firebase/php-jwt/CHANGELOG.md

@@ -0,0 +1,156 @@
+# Changelog
+
+## [6.8.1](https://github.com/firebase/php-jwt/compare/v6.8.0...v6.8.1) (2023-07-14)
+
+
+### Bug Fixes
+
+* accept float claims but round down to ignore them ([#492](https://github.com/firebase/php-jwt/issues/492)) ([3936842](https://github.com/firebase/php-jwt/commit/39368423beeaacb3002afa7dcb75baebf204fe7e))
+* different BeforeValidException messages for nbf and iat ([#526](https://github.com/firebase/php-jwt/issues/526)) ([0a53cf2](https://github.com/firebase/php-jwt/commit/0a53cf2986e45c2bcbf1a269f313ebf56a154ee4))
+
+## [6.8.0](https://github.com/firebase/php-jwt/compare/v6.7.0...v6.8.0) (2023-06-14)
+
+
+### Features
+
+* add support for P-384 curve ([#515](https://github.com/firebase/php-jwt/issues/515)) ([5de4323](https://github.com/firebase/php-jwt/commit/5de4323f4baf4d70bca8663bd87682a69c656c3d))
+
+
+### Bug Fixes
+
+* handle invalid http responses ([#508](https://github.com/firebase/php-jwt/issues/508)) ([91c39c7](https://github.com/firebase/php-jwt/commit/91c39c72b22fc3e1191e574089552c1f2041c718))
+
+## [6.7.0](https://github.com/firebase/php-jwt/compare/v6.6.0...v6.7.0) (2023-06-14)
+
+
+### Features
+
+* add ed25519 support to JWK (public keys) ([#452](https://github.com/firebase/php-jwt/issues/452)) ([e53979a](https://github.com/firebase/php-jwt/commit/e53979abae927de916a75b9d239cfda8ce32be2a))
+
+## [6.6.0](https://github.com/firebase/php-jwt/compare/v6.5.0...v6.6.0) (2023-06-13)
+
+
+### Features
+
+* allow get headers when decoding token ([#442](https://github.com/firebase/php-jwt/issues/442)) ([fb85f47](https://github.com/firebase/php-jwt/commit/fb85f47cfaeffdd94faf8defdf07164abcdad6c3))
+
+
+### Bug Fixes
+
+* only check iat if nbf is not used ([#493](https://github.com/firebase/php-jwt/issues/493)) ([398ccd2](https://github.com/firebase/php-jwt/commit/398ccd25ea12fa84b9e4f1085d5ff448c21ec797))
+
+## [6.5.0](https://github.com/firebase/php-jwt/compare/v6.4.0...v6.5.0) (2023-05-12)
+
+
+### Bug Fixes
+
+* allow KID of '0' ([#505](https://github.com/firebase/php-jwt/issues/505)) ([9dc46a9](https://github.com/firebase/php-jwt/commit/9dc46a9c3e5801294249cfd2554c5363c9f9326a))
+
+
+### Miscellaneous Chores
+
+* drop support for PHP 7.3 ([#495](https://github.com/firebase/php-jwt/issues/495))
+
+## [6.4.0](https://github.com/firebase/php-jwt/compare/v6.3.2...v6.4.0) (2023-02-08)
+
+
+### Features
+
+* add support for W3C ES256K ([#462](https://github.com/firebase/php-jwt/issues/462)) ([213924f](https://github.com/firebase/php-jwt/commit/213924f51936291fbbca99158b11bd4ae56c2c95))
+* improve caching by only decoding jwks when necessary ([#486](https://github.com/firebase/php-jwt/issues/486)) ([78d3ed1](https://github.com/firebase/php-jwt/commit/78d3ed1073553f7d0bbffa6c2010009a0d483d5c))
+
+## [6.3.2](https://github.com/firebase/php-jwt/compare/v6.3.1...v6.3.2) (2022-11-01)
+
+
+### Bug Fixes
+
+* check kid before using as array index ([bad1b04](https://github.com/firebase/php-jwt/commit/bad1b040d0c736bbf86814c6b5ae614f517cf7bd))
+
+## [6.3.1](https://github.com/firebase/php-jwt/compare/v6.3.0...v6.3.1) (2022-11-01)
+
+
+### Bug Fixes
+
+* casing of GET for PSR compat ([#451](https://github.com/firebase/php-jwt/issues/451)) ([60b52b7](https://github.com/firebase/php-jwt/commit/60b52b71978790eafcf3b95cfbd83db0439e8d22))
+* string interpolation format for php 8.2 ([#446](https://github.com/firebase/php-jwt/issues/446)) ([2e07d8a](https://github.com/firebase/php-jwt/commit/2e07d8a1524d12b69b110ad649f17461d068b8f2))
+
+## 6.3.0 / 2022-07-15
+
+ - Added ES256 support to JWK parsing ([#399](https://github.com/firebase/php-jwt/pull/399))
+ - Fixed potential caching error in `CachedKeySet` by caching jwks as strings ([#435](https://github.com/firebase/php-jwt/pull/435))
+
+## 6.2.0 / 2022-05-14
+
+ - Added `CachedKeySet` ([#397](https://github.com/firebase/php-jwt/pull/397))
+ - Added `$defaultAlg` parameter to `JWT::parseKey` and `JWT::parseKeySet` ([#426](https://github.com/firebase/php-jwt/pull/426)).
+
+## 6.1.0 / 2022-03-23
+
+ - Drop support for PHP 5.3, 5.4, 5.5, 5.6, and 7.0
+ - Add parameter typing and return types where possible
+
+## 6.0.0 / 2022-01-24
+
+ - **Backwards-Compatibility Breaking Changes**: See the [Release Notes](https://github.com/firebase/php-jwt/releases/tag/v6.0.0) for more information.
+ - New Key object to prevent key/algorithm type confusion (#365)
+ - Add JWK support (#273)
+ - Add ES256 support (#256)
+ - Add ES384 support (#324)
+ - Add Ed25519 support (#343)
+
+## 5.0.0 / 2017-06-26
+- Support RS384 and RS512.
+  See [#117](https://github.com/firebase/php-jwt/pull/117). Thanks [@joostfaassen](https://github.com/joostfaassen)!
+- Add an example for RS256 openssl.
+  See [#125](https://github.com/firebase/php-jwt/pull/125). Thanks [@akeeman](https://github.com/akeeman)!
+- Detect invalid Base64 encoding in signature.
+  See [#162](https://github.com/firebase/php-jwt/pull/162). Thanks [@psignoret](https://github.com/psignoret)!
+- Update `JWT::verify` to handle OpenSSL errors.
+  See [#159](https://github.com/firebase/php-jwt/pull/159). Thanks [@bshaffer](https://github.com/bshaffer)!
+- Add `array` type hinting to `decode` method
+  See [#101](https://github.com/firebase/php-jwt/pull/101). Thanks [@hywak](https://github.com/hywak)!
+- Add all JSON error types.
+  See [#110](https://github.com/firebase/php-jwt/pull/110). Thanks [@gbalduzzi](https://github.com/gbalduzzi)!
+- Bugfix 'kid' not in given key list.
+  See [#129](https://github.com/firebase/php-jwt/pull/129). Thanks [@stampycode](https://github.com/stampycode)!
+- Miscellaneous cleanup, documentation and test fixes.
+  See [#107](https://github.com/firebase/php-jwt/pull/107), [#115](https://github.com/firebase/php-jwt/pull/115),
+  [#160](https://github.com/firebase/php-jwt/pull/160), [#161](https://github.com/firebase/php-jwt/pull/161), and
+  [#165](https://github.com/firebase/php-jwt/pull/165). Thanks [@akeeman](https://github.com/akeeman),
+  [@chinedufn](https://github.com/chinedufn), and [@bshaffer](https://github.com/bshaffer)!
+
+## 4.0.0 / 2016-07-17
+- Add support for late static binding. See [#88](https://github.com/firebase/php-jwt/pull/88) for details. Thanks to [@chappy84](https://github.com/chappy84)!
+- Use static `$timestamp` instead of `time()` to improve unit testing. See [#93](https://github.com/firebase/php-jwt/pull/93) for details. Thanks to [@josephmcdermott](https://github.com/josephmcdermott)!
+- Fixes to exceptions classes. See [#81](https://github.com/firebase/php-jwt/pull/81) for details. Thanks to [@Maks3w](https://github.com/Maks3w)!
+- Fixes to PHPDoc. See [#76](https://github.com/firebase/php-jwt/pull/76) for details. Thanks to [@akeeman](https://github.com/akeeman)!
+
+## 3.0.0 / 2015-07-22
+- Minimum PHP version updated from `5.2.0` to `5.3.0`.
+- Add `\Firebase\JWT` namespace. See
+[#59](https://github.com/firebase/php-jwt/pull/59) for details. Thanks to
+[@Dashron](https://github.com/Dashron)!
+- Require a non-empty key to decode and verify a JWT. See
+[#60](https://github.com/firebase/php-jwt/pull/60) for details. Thanks to
+[@sjones608](https://github.com/sjones608)!
+- Cleaner documentation blocks in the code. See
+[#62](https://github.com/firebase/php-jwt/pull/62) for details. Thanks to
+[@johanderuijter](https://github.com/johanderuijter)!
+
+## 2.2.0 / 2015-06-22
+- Add support for adding custom, optional JWT headers to `JWT::encode()`. See
+[#53](https://github.com/firebase/php-jwt/pull/53/files) for details. Thanks to
+[@mcocaro](https://github.com/mcocaro)!
+
+## 2.1.0 / 2015-05-20
+- Add support for adding a leeway to `JWT:decode()` that accounts for clock skew
+between signing and verifying entities. Thanks to [@lcabral](https://github.com/lcabral)!
+- Add support for passing an object implementing the `ArrayAccess` interface for
+`$keys` argument in `JWT::decode()`. Thanks to [@aztech-dev](https://github.com/aztech-dev)!
+
+## 2.0.0 / 2015-04-01
+- **Note**: It is strongly recommended that you update to > v2.0.0 to address
+  known security vulnerabilities in prior versions when both symmetric and
+  asymmetric keys are used together.
+- Update signature for `JWT::decode(...)` to require an array of supported
+  algorithms to use when verifying token signatures.

+ 30 - 0
api/v1/vendor/firebase/php-jwt/LICENSE

@@ -0,0 +1,30 @@
+Copyright (c) 2011, Neuman Vong
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of the copyright holder nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 424 - 0
api/v1/vendor/firebase/php-jwt/README.md

@@ -0,0 +1,424 @@
+![Build Status](https://github.com/firebase/php-jwt/actions/workflows/tests.yml/badge.svg)
+[![Latest Stable Version](https://poser.pugx.org/firebase/php-jwt/v/stable)](https://packagist.org/packages/firebase/php-jwt)
+[![Total Downloads](https://poser.pugx.org/firebase/php-jwt/downloads)](https://packagist.org/packages/firebase/php-jwt)
+[![License](https://poser.pugx.org/firebase/php-jwt/license)](https://packagist.org/packages/firebase/php-jwt)
+
+PHP-JWT
+=======
+A simple library to encode and decode JSON Web Tokens (JWT) in PHP, conforming to [RFC 7519](https://tools.ietf.org/html/rfc7519).
+
+Installation
+------------
+
+Use composer to manage your dependencies and download PHP-JWT:
+
+```bash
+composer require firebase/php-jwt
+```
+
+Optionally, install the `paragonie/sodium_compat` package from composer if your
+php is < 7.2 or does not have libsodium installed:
+
+```bash
+composer require paragonie/sodium_compat
+```
+
+Example
+-------
+```php
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+
+$key = 'example_key';
+$payload = [
+    'iss' => 'http://example.org',
+    'aud' => 'http://example.com',
+    'iat' => 1356999524,
+    'nbf' => 1357000000
+];
+
+/**
+ * IMPORTANT:
+ * You must specify supported algorithms for your application. See
+ * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
+ * for a list of spec-compliant algorithms.
+ */
+$jwt = JWT::encode($payload, $key, 'HS256');
+$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
+print_r($decoded);
+
+// Pass a stdClass in as the third parameter to get the decoded header values
+$decoded = JWT::decode($jwt, new Key($key, 'HS256'), $headers = new stdClass());
+print_r($headers);
+
+/*
+ NOTE: This will now be an object instead of an associative array. To get
+ an associative array, you will need to cast it as such:
+*/
+
+$decoded_array = (array) $decoded;
+
+/**
+ * You can add a leeway to account for when there is a clock skew times between
+ * the signing and verifying servers. It is recommended that this leeway should
+ * not be bigger than a few minutes.
+ *
+ * Source: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef
+ */
+JWT::$leeway = 60; // $leeway in seconds
+$decoded = JWT::decode($jwt, new Key($key, 'HS256'));
+```
+Example encode/decode headers
+-------
+Decoding the JWT headers without verifying the JWT first is NOT recommended, and is not supported by
+this library. This is because without verifying the JWT, the header values could have been tampered with.
+Any value pulled from an unverified header should be treated as if it could be any string sent in from an
+attacker.  If this is something you still want to do in your application for whatever reason, it's possible to
+decode the header values manually simply by calling `json_decode` and `base64_decode` on the JWT
+header part:
+```php
+use Firebase\JWT\JWT;
+
+$key = 'example_key';
+$payload = [
+    'iss' => 'http://example.org',
+    'aud' => 'http://example.com',
+    'iat' => 1356999524,
+    'nbf' => 1357000000
+];
+
+$headers = [
+    'x-forwarded-for' => 'www.google.com'
+];
+
+// Encode headers in the JWT string
+$jwt = JWT::encode($payload, $key, 'HS256', null, $headers);
+
+// Decode headers from the JWT string WITHOUT validation
+// **IMPORTANT**: This operation is vulnerable to attacks, as the JWT has not yet been verified.
+// These headers could be any value sent by an attacker.
+list($headersB64, $payloadB64, $sig) = explode('.', $jwt);
+$decoded = json_decode(base64_decode($headersB64), true);
+
+print_r($decoded);
+```
+Example with RS256 (openssl)
+----------------------------
+```php
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+
+$privateKey = <<<EOD
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAuzWHNM5f+amCjQztc5QTfJfzCC5J4nuW+L/aOxZ4f8J3Frew
+M2c/dufrnmedsApb0By7WhaHlcqCh/ScAPyJhzkPYLae7bTVro3hok0zDITR8F6S
+JGL42JAEUk+ILkPI+DONM0+3vzk6Kvfe548tu4czCuqU8BGVOlnp6IqBHhAswNMM
+78pos/2z0CjPM4tbeXqSTTbNkXRboxjU29vSopcT51koWOgiTf3C7nJUoMWZHZI5
+HqnIhPAG9yv8HAgNk6CMk2CadVHDo4IxjxTzTTqo1SCSH2pooJl9O8at6kkRYsrZ
+WwsKlOFE2LUce7ObnXsYihStBUDoeBQlGG/BwQIDAQABAoIBAFtGaOqNKGwggn9k
+6yzr6GhZ6Wt2rh1Xpq8XUz514UBhPxD7dFRLpbzCrLVpzY80LbmVGJ9+1pJozyWc
+VKeCeUdNwbqkr240Oe7GTFmGjDoxU+5/HX/SJYPpC8JZ9oqgEA87iz+WQX9hVoP2
+oF6EB4ckDvXmk8FMwVZW2l2/kd5mrEVbDaXKxhvUDf52iVD+sGIlTif7mBgR99/b
+c3qiCnxCMmfYUnT2eh7Vv2LhCR/G9S6C3R4lA71rEyiU3KgsGfg0d82/XWXbegJW
+h3QbWNtQLxTuIvLq5aAryV3PfaHlPgdgK0ft6ocU2de2FagFka3nfVEyC7IUsNTK
+bq6nhAECgYEA7d/0DPOIaItl/8BWKyCuAHMss47j0wlGbBSHdJIiS55akMvnAG0M
+39y22Qqfzh1at9kBFeYeFIIU82ZLF3xOcE3z6pJZ4Dyvx4BYdXH77odo9uVK9s1l
+3T3BlMcqd1hvZLMS7dviyH79jZo4CXSHiKzc7pQ2YfK5eKxKqONeXuECgYEAyXlG
+vonaus/YTb1IBei9HwaccnQ/1HRn6MvfDjb7JJDIBhNClGPt6xRlzBbSZ73c2QEC
+6Fu9h36K/HZ2qcLd2bXiNyhIV7b6tVKk+0Psoj0dL9EbhsD1OsmE1nTPyAc9XZbb
+OPYxy+dpBCUA8/1U9+uiFoCa7mIbWcSQ+39gHuECgYAz82pQfct30aH4JiBrkNqP
+nJfRq05UY70uk5k1u0ikLTRoVS/hJu/d4E1Kv4hBMqYCavFSwAwnvHUo51lVCr/y
+xQOVYlsgnwBg2MX4+GjmIkqpSVCC8D7j/73MaWb746OIYZervQ8dbKahi2HbpsiG
+8AHcVSA/agxZr38qvWV54QKBgCD5TlDE8x18AuTGQ9FjxAAd7uD0kbXNz2vUYg9L
+hFL5tyL3aAAtUrUUw4xhd9IuysRhW/53dU+FsG2dXdJu6CxHjlyEpUJl2iZu/j15
+YnMzGWHIEX8+eWRDsw/+Ujtko/B7TinGcWPz3cYl4EAOiCeDUyXnqnO1btCEUU44
+DJ1BAoGBAJuPD27ErTSVtId90+M4zFPNibFP50KprVdc8CR37BE7r8vuGgNYXmnI
+RLnGP9p3pVgFCktORuYS2J/6t84I3+A17nEoB4xvhTLeAinAW/uTQOUmNicOP4Ek
+2MsLL2kHgL8bLTmvXV4FX+PXphrDKg1XxzOYn0otuoqdAQrkK4og
+-----END RSA PRIVATE KEY-----
+EOD;
+
+$publicKey = <<<EOD
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzWHNM5f+amCjQztc5QT
+fJfzCC5J4nuW+L/aOxZ4f8J3FrewM2c/dufrnmedsApb0By7WhaHlcqCh/ScAPyJ
+hzkPYLae7bTVro3hok0zDITR8F6SJGL42JAEUk+ILkPI+DONM0+3vzk6Kvfe548t
+u4czCuqU8BGVOlnp6IqBHhAswNMM78pos/2z0CjPM4tbeXqSTTbNkXRboxjU29vS
+opcT51koWOgiTf3C7nJUoMWZHZI5HqnIhPAG9yv8HAgNk6CMk2CadVHDo4IxjxTz
+TTqo1SCSH2pooJl9O8at6kkRYsrZWwsKlOFE2LUce7ObnXsYihStBUDoeBQlGG/B
+wQIDAQAB
+-----END PUBLIC KEY-----
+EOD;
+
+$payload = [
+    'iss' => 'example.org',
+    'aud' => 'example.com',
+    'iat' => 1356999524,
+    'nbf' => 1357000000
+];
+
+$jwt = JWT::encode($payload, $privateKey, 'RS256');
+echo "Encode:\n" . print_r($jwt, true) . "\n";
+
+$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
+
+/*
+ NOTE: This will now be an object instead of an associative array. To get
+ an associative array, you will need to cast it as such:
+*/
+
+$decoded_array = (array) $decoded;
+echo "Decode:\n" . print_r($decoded_array, true) . "\n";
+```
+
+Example with a passphrase
+-------------------------
+
+```php
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+
+// Your passphrase
+$passphrase = '[YOUR_PASSPHRASE]';
+
+// Your private key file with passphrase
+// Can be generated with "ssh-keygen -t rsa -m pem"
+$privateKeyFile = '/path/to/key-with-passphrase.pem';
+
+// Create a private key of type "resource"
+$privateKey = openssl_pkey_get_private(
+    file_get_contents($privateKeyFile),
+    $passphrase
+);
+
+$payload = [
+    'iss' => 'example.org',
+    'aud' => 'example.com',
+    'iat' => 1356999524,
+    'nbf' => 1357000000
+];
+
+$jwt = JWT::encode($payload, $privateKey, 'RS256');
+echo "Encode:\n" . print_r($jwt, true) . "\n";
+
+// Get public key from the private key, or pull from from a file.
+$publicKey = openssl_pkey_get_details($privateKey)['key'];
+
+$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));
+echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
+```
+
+Example with EdDSA (libsodium and Ed25519 signature)
+----------------------------
+```php
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+
+// Public and private keys are expected to be Base64 encoded. The last
+// non-empty line is used so that keys can be generated with
+// sodium_crypto_sign_keypair(). The secret keys generated by other tools may
+// need to be adjusted to match the input expected by libsodium.
+
+$keyPair = sodium_crypto_sign_keypair();
+
+$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));
+
+$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));
+
+$payload = [
+    'iss' => 'example.org',
+    'aud' => 'example.com',
+    'iat' => 1356999524,
+    'nbf' => 1357000000
+];
+
+$jwt = JWT::encode($payload, $privateKey, 'EdDSA');
+echo "Encode:\n" . print_r($jwt, true) . "\n";
+
+$decoded = JWT::decode($jwt, new Key($publicKey, 'EdDSA'));
+echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
+````
+
+Example with multiple keys
+--------------------------
+```php
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+
+// Example RSA keys from previous example
+// $privateKey1 = '...';
+// $publicKey1 = '...';
+
+// Example EdDSA keys from previous example
+// $privateKey2 = '...';
+// $publicKey2 = '...';
+
+$payload = [
+    'iss' => 'example.org',
+    'aud' => 'example.com',
+    'iat' => 1356999524,
+    'nbf' => 1357000000
+];
+
+$jwt1 = JWT::encode($payload, $privateKey1, 'RS256', 'kid1');
+$jwt2 = JWT::encode($payload, $privateKey2, 'EdDSA', 'kid2');
+echo "Encode 1:\n" . print_r($jwt1, true) . "\n";
+echo "Encode 2:\n" . print_r($jwt2, true) . "\n";
+
+$keys = [
+    'kid1' => new Key($publicKey1, 'RS256'),
+    'kid2' => new Key($publicKey2, 'EdDSA'),
+];
+
+$decoded1 = JWT::decode($jwt1, $keys);
+$decoded2 = JWT::decode($jwt2, $keys);
+
+echo "Decode 1:\n" . print_r((array) $decoded1, true) . "\n";
+echo "Decode 2:\n" . print_r((array) $decoded2, true) . "\n";
+```
+
+Using JWKs
+----------
+
+```php
+use Firebase\JWT\JWK;
+use Firebase\JWT\JWT;
+
+// Set of keys. The "keys" key is required. For example, the JSON response to
+// this endpoint: https://www.gstatic.com/iap/verify/public_key-jwk
+$jwks = ['keys' => []];
+
+// JWK::parseKeySet($jwks) returns an associative array of **kid** to Firebase\JWT\Key
+// objects. Pass this as the second parameter to JWT::decode.
+JWT::decode($payload, JWK::parseKeySet($jwks));
+```
+
+Using Cached Key Sets
+---------------------
+
+The `CachedKeySet` class can be used to fetch and cache JWKS (JSON Web Key Sets) from a public URI.
+This has the following advantages:
+
+1. The results are cached for performance.
+2. If an unrecognized key is requested, the cache is refreshed, to accomodate for key rotation.
+3. If rate limiting is enabled, the JWKS URI will not make more than 10 requests a second.
+
+```php
+use Firebase\JWT\CachedKeySet;
+use Firebase\JWT\JWT;
+
+// The URI for the JWKS you wish to cache the results from
+$jwksUri = 'https://www.gstatic.com/iap/verify/public_key-jwk';
+
+// Create an HTTP client (can be any PSR-7 compatible HTTP client)
+$httpClient = new GuzzleHttp\Client();
+
+// Create an HTTP request factory (can be any PSR-17 compatible HTTP request factory)
+$httpFactory = new GuzzleHttp\Psr\HttpFactory();
+
+// Create a cache item pool (can be any PSR-6 compatible cache item pool)
+$cacheItemPool = Phpfastcache\CacheManager::getInstance('files');
+
+$keySet = new CachedKeySet(
+    $jwksUri,
+    $httpClient,
+    $httpFactory,
+    $cacheItemPool,
+    null, // $expiresAfter int seconds to set the JWKS to expire
+    true  // $rateLimit    true to enable rate limit of 10 RPS on lookup of invalid keys
+);
+
+$jwt = 'eyJhbGci...'; // Some JWT signed by a key from the $jwkUri above
+$decoded = JWT::decode($jwt, $keySet);
+```
+
+Miscellaneous
+-------------
+
+#### Exception Handling
+
+When a call to `JWT::decode` is invalid, it will throw one of the following exceptions:
+
+```php
+use Firebase\JWT\JWT;
+use Firebase\JWT\SignatureInvalidException;
+use Firebase\JWT\BeforeValidException;
+use Firebase\JWT\ExpiredException;
+use DomainException;
+use InvalidArgumentException;
+use UnexpectedValueException;
+
+try {
+    $decoded = JWT::decode($payload, $keys);
+} catch (InvalidArgumentException $e) {
+    // provided key/key-array is empty or malformed.
+} catch (DomainException $e) {
+    // provided algorithm is unsupported OR
+    // provided key is invalid OR
+    // unknown error thrown in openSSL or libsodium OR
+    // libsodium is required but not available.
+} catch (SignatureInvalidException $e) {
+    // provided JWT signature verification failed.
+} catch (BeforeValidException $e) {
+    // provided JWT is trying to be used before "nbf" claim OR
+    // provided JWT is trying to be used before "iat" claim.
+} catch (ExpiredException $e) {
+    // provided JWT is trying to be used after "exp" claim.
+} catch (UnexpectedValueException $e) {
+    // provided JWT is malformed OR
+    // provided JWT is missing an algorithm / using an unsupported algorithm OR
+    // provided JWT algorithm does not match provided key OR
+    // provided key ID in key/key-array is empty or invalid.
+}
+```
+
+All exceptions in the `Firebase\JWT` namespace extend `UnexpectedValueException`, and can be simplified
+like this:
+
+```php
+use Firebase\JWT\JWT;
+use UnexpectedValueException;
+try {
+    $decoded = JWT::decode($payload, $keys);
+} catch (LogicException $e) {
+    // errors having to do with environmental setup or malformed JWT Keys
+} catch (UnexpectedValueException $e) {
+    // errors having to do with JWT signature and claims
+}
+```
+
+#### Casting to array
+
+The return value of `JWT::decode` is the generic PHP object `stdClass`. If you'd like to handle with arrays
+instead, you can do the following:
+
+```php
+// return type is stdClass
+$decoded = JWT::decode($payload, $keys);
+
+// cast to array
+$decoded = json_decode(json_encode($decoded), true);
+```
+
+Tests
+-----
+Run the tests using phpunit:
+
+```bash
+$ pear install PHPUnit
+$ phpunit --configuration phpunit.xml.dist
+PHPUnit 3.7.10 by Sebastian Bergmann.
+.....
+Time: 0 seconds, Memory: 2.50Mb
+OK (5 tests, 5 assertions)
+```
+
+New Lines in private keys
+-----
+
+If your private key contains `\n` characters, be sure to wrap it in double quotes `""`
+and not single quotes `''` in order to properly interpret the escaped characters.
+
+License
+-------
+[3-Clause BSD](http://opensource.org/licenses/BSD-3-Clause).

+ 42 - 0
api/v1/vendor/firebase/php-jwt/composer.json

@@ -0,0 +1,42 @@
+{
+    "name": "firebase/php-jwt",
+    "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.",
+    "homepage": "https://github.com/firebase/php-jwt",
+    "keywords": [
+        "php",
+        "jwt"
+    ],
+    "authors": [
+        {
+            "name": "Neuman Vong",
+            "email": "neuman+pear@twilio.com",
+            "role": "Developer"
+        },
+        {
+            "name": "Anant Narayanan",
+            "email": "anant@php.net",
+            "role": "Developer"
+        }
+    ],
+    "license": "BSD-3-Clause",
+    "require": {
+        "php": "^7.4||^8.0"
+    },
+    "suggest": {
+        "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present",
+        "ext-sodium": "Support EdDSA (Ed25519) signatures"
+    },
+    "autoload": {
+        "psr-4": {
+            "Firebase\\JWT\\": "src"
+        }
+    },
+    "require-dev": {
+        "guzzlehttp/guzzle": "^6.5||^7.4",
+        "phpspec/prophecy-phpunit": "^2.0",
+        "phpunit/phpunit": "^9.5",
+        "psr/cache": "^1.0||^2.0",
+        "psr/http-client": "^1.0",
+        "psr/http-factory": "^1.0"
+    }
+}

+ 7 - 0
api/v1/vendor/firebase/php-jwt/src/BeforeValidException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Firebase\JWT;
+
+class BeforeValidException extends \UnexpectedValueException
+{
+}

+ 268 - 0
api/v1/vendor/firebase/php-jwt/src/CachedKeySet.php

@@ -0,0 +1,268 @@
+<?php
+
+namespace Firebase\JWT;
+
+use ArrayAccess;
+use InvalidArgumentException;
+use LogicException;
+use OutOfBoundsException;
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use RuntimeException;
+use UnexpectedValueException;
+
+/**
+ * @implements ArrayAccess<string, Key>
+ */
+class CachedKeySet implements ArrayAccess
+{
+    /**
+     * @var string
+     */
+    private $jwksUri;
+    /**
+     * @var ClientInterface
+     */
+    private $httpClient;
+    /**
+     * @var RequestFactoryInterface
+     */
+    private $httpFactory;
+    /**
+     * @var CacheItemPoolInterface
+     */
+    private $cache;
+    /**
+     * @var ?int
+     */
+    private $expiresAfter;
+    /**
+     * @var ?CacheItemInterface
+     */
+    private $cacheItem;
+    /**
+     * @var array<string, array<mixed>>
+     */
+    private $keySet;
+    /**
+     * @var string
+     */
+    private $cacheKey;
+    /**
+     * @var string
+     */
+    private $cacheKeyPrefix = 'jwks';
+    /**
+     * @var int
+     */
+    private $maxKeyLength = 64;
+    /**
+     * @var bool
+     */
+    private $rateLimit;
+    /**
+     * @var string
+     */
+    private $rateLimitCacheKey;
+    /**
+     * @var int
+     */
+    private $maxCallsPerMinute = 10;
+    /**
+     * @var string|null
+     */
+    private $defaultAlg;
+
+    public function __construct(
+        string $jwksUri,
+        ClientInterface $httpClient,
+        RequestFactoryInterface $httpFactory,
+        CacheItemPoolInterface $cache,
+        int $expiresAfter = null,
+        bool $rateLimit = false,
+        string $defaultAlg = null
+    ) {
+        $this->jwksUri = $jwksUri;
+        $this->httpClient = $httpClient;
+        $this->httpFactory = $httpFactory;
+        $this->cache = $cache;
+        $this->expiresAfter = $expiresAfter;
+        $this->rateLimit = $rateLimit;
+        $this->defaultAlg = $defaultAlg;
+        $this->setCacheKeys();
+    }
+
+    /**
+     * @param string $keyId
+     * @return Key
+     */
+    public function offsetGet($keyId): Key
+    {
+        if (!$this->keyIdExists($keyId)) {
+            throw new OutOfBoundsException('Key ID not found');
+        }
+        return JWK::parseKey($this->keySet[$keyId], $this->defaultAlg);
+    }
+
+    /**
+     * @param string $keyId
+     * @return bool
+     */
+    public function offsetExists($keyId): bool
+    {
+        return $this->keyIdExists($keyId);
+    }
+
+    /**
+     * @param string $offset
+     * @param Key $value
+     */
+    public function offsetSet($offset, $value): void
+    {
+        throw new LogicException('Method not implemented');
+    }
+
+    /**
+     * @param string $offset
+     */
+    public function offsetUnset($offset): void
+    {
+        throw new LogicException('Method not implemented');
+    }
+
+    /**
+     * @return array<mixed>
+     */
+    private function formatJwksForCache(string $jwks): array
+    {
+        $jwks = json_decode($jwks, true);
+
+        if (!isset($jwks['keys'])) {
+            throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
+        }
+
+        if (empty($jwks['keys'])) {
+            throw new InvalidArgumentException('JWK Set did not contain any keys');
+        }
+
+        $keys = [];
+        foreach ($jwks['keys'] as $k => $v) {
+            $kid = isset($v['kid']) ? $v['kid'] : $k;
+            $keys[(string) $kid] = $v;
+        }
+
+        return $keys;
+    }
+
+    private function keyIdExists(string $keyId): bool
+    {
+        if (null === $this->keySet) {
+            $item = $this->getCacheItem();
+            // Try to load keys from cache
+            if ($item->isHit()) {
+                // item found! retrieve it
+                $this->keySet = $item->get();
+                // If the cached item is a string, the JWKS response was cached (previous behavior).
+                // Parse this into expected format array<kid, jwk> instead.
+                if (\is_string($this->keySet)) {
+                    $this->keySet = $this->formatJwksForCache($this->keySet);
+                }
+            }
+        }
+
+        if (!isset($this->keySet[$keyId])) {
+            if ($this->rateLimitExceeded()) {
+                return false;
+            }
+            $request = $this->httpFactory->createRequest('GET', $this->jwksUri);
+            $jwksResponse = $this->httpClient->sendRequest($request);
+            if ($jwksResponse->getStatusCode() !== 200) {
+                throw new UnexpectedValueException(
+                    sprintf('HTTP Error: %d %s for URI "%s"',
+                        $jwksResponse->getStatusCode(),
+                        $jwksResponse->getReasonPhrase(),
+                        $this->jwksUri,
+                    ),
+                    $jwksResponse->getStatusCode()
+                );
+            }
+            $this->keySet = $this->formatJwksForCache((string) $jwksResponse->getBody());
+
+            if (!isset($this->keySet[$keyId])) {
+                return false;
+            }
+
+            $item = $this->getCacheItem();
+            $item->set($this->keySet);
+            if ($this->expiresAfter) {
+                $item->expiresAfter($this->expiresAfter);
+            }
+            $this->cache->save($item);
+        }
+
+        return true;
+    }
+
+    private function rateLimitExceeded(): bool
+    {
+        if (!$this->rateLimit) {
+            return false;
+        }
+
+        $cacheItem = $this->cache->getItem($this->rateLimitCacheKey);
+        if (!$cacheItem->isHit()) {
+            $cacheItem->expiresAfter(1); // # of calls are cached each minute
+        }
+
+        $callsPerMinute = (int) $cacheItem->get();
+        if (++$callsPerMinute > $this->maxCallsPerMinute) {
+            return true;
+        }
+        $cacheItem->set($callsPerMinute);
+        $this->cache->save($cacheItem);
+        return false;
+    }
+
+    private function getCacheItem(): CacheItemInterface
+    {
+        if (\is_null($this->cacheItem)) {
+            $this->cacheItem = $this->cache->getItem($this->cacheKey);
+        }
+
+        return $this->cacheItem;
+    }
+
+    private function setCacheKeys(): void
+    {
+        if (empty($this->jwksUri)) {
+            throw new RuntimeException('JWKS URI is empty');
+        }
+
+        // ensure we do not have illegal characters
+        $key = preg_replace('|[^a-zA-Z0-9_\.!]|', '', $this->jwksUri);
+
+        // add prefix
+        $key = $this->cacheKeyPrefix . $key;
+
+        // Hash keys if they exceed $maxKeyLength of 64
+        if (\strlen($key) > $this->maxKeyLength) {
+            $key = substr(hash('sha256', $key), 0, $this->maxKeyLength);
+        }
+
+        $this->cacheKey = $key;
+
+        if ($this->rateLimit) {
+            // add prefix
+            $rateLimitKey = $this->cacheKeyPrefix . 'ratelimit' . $key;
+
+            // Hash keys if they exceed $maxKeyLength of 64
+            if (\strlen($rateLimitKey) > $this->maxKeyLength) {
+                $rateLimitKey = substr(hash('sha256', $rateLimitKey), 0, $this->maxKeyLength);
+            }
+
+            $this->rateLimitCacheKey = $rateLimitKey;
+        }
+    }
+}

+ 7 - 0
api/v1/vendor/firebase/php-jwt/src/ExpiredException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Firebase\JWT;
+
+class ExpiredException extends \UnexpectedValueException
+{
+}

+ 349 - 0
api/v1/vendor/firebase/php-jwt/src/JWK.php

@@ -0,0 +1,349 @@
+<?php
+
+namespace Firebase\JWT;
+
+use DomainException;
+use InvalidArgumentException;
+use UnexpectedValueException;
+
+/**
+ * JSON Web Key implementation, based on this spec:
+ * https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41
+ *
+ * PHP version 5
+ *
+ * @category Authentication
+ * @package  Authentication_JWT
+ * @author   Bui Sy Nguyen <nguyenbs@gmail.com>
+ * @license  http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
+ * @link     https://github.com/firebase/php-jwt
+ */
+class JWK
+{
+    private const OID = '1.2.840.10045.2.1';
+    private const ASN1_OBJECT_IDENTIFIER = 0x06;
+    private const ASN1_SEQUENCE = 0x10; // also defined in JWT
+    private const ASN1_BIT_STRING = 0x03;
+    private const EC_CURVES = [
+        'P-256' => '1.2.840.10045.3.1.7', // Len: 64
+        'secp256k1' => '1.3.132.0.10', // Len: 64
+        'P-384' => '1.3.132.0.34', // Len: 96
+        // 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
+    ];
+
+    // For keys with "kty" equal to "OKP" (Octet Key Pair), the "crv" parameter must contain the key subtype.
+    // This library supports the following subtypes:
+    private const OKP_SUBTYPES = [
+        'Ed25519' => true, // RFC 8037
+    ];
+
+    /**
+     * Parse a set of JWK keys
+     *
+     * @param array<mixed> $jwks The JSON Web Key Set as an associative array
+     * @param string       $defaultAlg The algorithm for the Key object if "alg" is not set in the
+     *                                 JSON Web Key Set
+     *
+     * @return array<string, Key> An associative array of key IDs (kid) to Key objects
+     *
+     * @throws InvalidArgumentException     Provided JWK Set is empty
+     * @throws UnexpectedValueException     Provided JWK Set was invalid
+     * @throws DomainException              OpenSSL failure
+     *
+     * @uses parseKey
+     */
+    public static function parseKeySet(array $jwks, string $defaultAlg = null): array
+    {
+        $keys = [];
+
+        if (!isset($jwks['keys'])) {
+            throw new UnexpectedValueException('"keys" member must exist in the JWK Set');
+        }
+
+        if (empty($jwks['keys'])) {
+            throw new InvalidArgumentException('JWK Set did not contain any keys');
+        }
+
+        foreach ($jwks['keys'] as $k => $v) {
+            $kid = isset($v['kid']) ? $v['kid'] : $k;
+            if ($key = self::parseKey($v, $defaultAlg)) {
+                $keys[(string) $kid] = $key;
+            }
+        }
+
+        if (0 === \count($keys)) {
+            throw new UnexpectedValueException('No supported algorithms found in JWK Set');
+        }
+
+        return $keys;
+    }
+
+    /**
+     * Parse a JWK key
+     *
+     * @param array<mixed> $jwk An individual JWK
+     * @param string       $defaultAlg The algorithm for the Key object if "alg" is not set in the
+     *                                 JSON Web Key Set
+     *
+     * @return Key The key object for the JWK
+     *
+     * @throws InvalidArgumentException     Provided JWK is empty
+     * @throws UnexpectedValueException     Provided JWK was invalid
+     * @throws DomainException              OpenSSL failure
+     *
+     * @uses createPemFromModulusAndExponent
+     */
+    public static function parseKey(array $jwk, string $defaultAlg = null): ?Key
+    {
+        if (empty($jwk)) {
+            throw new InvalidArgumentException('JWK must not be empty');
+        }
+
+        if (!isset($jwk['kty'])) {
+            throw new UnexpectedValueException('JWK must contain a "kty" parameter');
+        }
+
+        if (!isset($jwk['alg'])) {
+            if (\is_null($defaultAlg)) {
+                // The "alg" parameter is optional in a KTY, but an algorithm is required
+                // for parsing in this library. Use the $defaultAlg parameter when parsing the
+                // key set in order to prevent this error.
+                // @see https://datatracker.ietf.org/doc/html/rfc7517#section-4.4
+                throw new UnexpectedValueException('JWK must contain an "alg" parameter');
+            }
+            $jwk['alg'] = $defaultAlg;
+        }
+
+        switch ($jwk['kty']) {
+            case 'RSA':
+                if (!empty($jwk['d'])) {
+                    throw new UnexpectedValueException('RSA private keys are not supported');
+                }
+                if (!isset($jwk['n']) || !isset($jwk['e'])) {
+                    throw new UnexpectedValueException('RSA keys must contain values for both "n" and "e"');
+                }
+
+                $pem = self::createPemFromModulusAndExponent($jwk['n'], $jwk['e']);
+                $publicKey = \openssl_pkey_get_public($pem);
+                if (false === $publicKey) {
+                    throw new DomainException(
+                        'OpenSSL error: ' . \openssl_error_string()
+                    );
+                }
+                return new Key($publicKey, $jwk['alg']);
+            case 'EC':
+                if (isset($jwk['d'])) {
+                    // The key is actually a private key
+                    throw new UnexpectedValueException('Key data must be for a public key');
+                }
+
+                if (empty($jwk['crv'])) {
+                    throw new UnexpectedValueException('crv not set');
+                }
+
+                if (!isset(self::EC_CURVES[$jwk['crv']])) {
+                    throw new DomainException('Unrecognised or unsupported EC curve');
+                }
+
+                if (empty($jwk['x']) || empty($jwk['y'])) {
+                    throw new UnexpectedValueException('x and y not set');
+                }
+
+                $publicKey = self::createPemFromCrvAndXYCoordinates($jwk['crv'], $jwk['x'], $jwk['y']);
+                return new Key($publicKey, $jwk['alg']);
+            case 'OKP':
+                if (isset($jwk['d'])) {
+                    // The key is actually a private key
+                    throw new UnexpectedValueException('Key data must be for a public key');
+                }
+
+                if (!isset($jwk['crv'])) {
+                    throw new UnexpectedValueException('crv not set');
+                }
+
+                if (empty(self::OKP_SUBTYPES[$jwk['crv']])) {
+                    throw new DomainException('Unrecognised or unsupported OKP key subtype');
+                }
+
+                if (empty($jwk['x'])) {
+                    throw new UnexpectedValueException('x not set');
+                }
+
+                // This library works internally with EdDSA keys (Ed25519) encoded in standard base64.
+                $publicKey = JWT::convertBase64urlToBase64($jwk['x']);
+                return new Key($publicKey, $jwk['alg']);
+            default:
+                break;
+        }
+
+        return null;
+    }
+
+    /**
+     * Converts the EC JWK values to pem format.
+     *
+     * @param   string  $crv The EC curve (only P-256 & P-384 is supported)
+     * @param   string  $x   The EC x-coordinate
+     * @param   string  $y   The EC y-coordinate
+     *
+     * @return  string
+     */
+    private static function createPemFromCrvAndXYCoordinates(string $crv, string $x, string $y): string
+    {
+        $pem =
+            self::encodeDER(
+                self::ASN1_SEQUENCE,
+                self::encodeDER(
+                    self::ASN1_SEQUENCE,
+                    self::encodeDER(
+                        self::ASN1_OBJECT_IDENTIFIER,
+                        self::encodeOID(self::OID)
+                    )
+                    . self::encodeDER(
+                        self::ASN1_OBJECT_IDENTIFIER,
+                        self::encodeOID(self::EC_CURVES[$crv])
+                    )
+                ) .
+                self::encodeDER(
+                    self::ASN1_BIT_STRING,
+                    \chr(0x00) . \chr(0x04)
+                    . JWT::urlsafeB64Decode($x)
+                    . JWT::urlsafeB64Decode($y)
+                )
+            );
+
+        return sprintf(
+            "-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
+            wordwrap(base64_encode($pem), 64, "\n", true)
+        );
+    }
+
+    /**
+     * Create a public key represented in PEM format from RSA modulus and exponent information
+     *
+     * @param string $n The RSA modulus encoded in Base64
+     * @param string $e The RSA exponent encoded in Base64
+     *
+     * @return string The RSA public key represented in PEM format
+     *
+     * @uses encodeLength
+     */
+    private static function createPemFromModulusAndExponent(
+        string $n,
+        string $e
+    ): string {
+        $mod = JWT::urlsafeB64Decode($n);
+        $exp = JWT::urlsafeB64Decode($e);
+
+        $modulus = \pack('Ca*a*', 2, self::encodeLength(\strlen($mod)), $mod);
+        $publicExponent = \pack('Ca*a*', 2, self::encodeLength(\strlen($exp)), $exp);
+
+        $rsaPublicKey = \pack(
+            'Ca*a*a*',
+            48,
+            self::encodeLength(\strlen($modulus) + \strlen($publicExponent)),
+            $modulus,
+            $publicExponent
+        );
+
+        // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
+        $rsaOID = \pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
+        $rsaPublicKey = \chr(0) . $rsaPublicKey;
+        $rsaPublicKey = \chr(3) . self::encodeLength(\strlen($rsaPublicKey)) . $rsaPublicKey;
+
+        $rsaPublicKey = \pack(
+            'Ca*a*',
+            48,
+            self::encodeLength(\strlen($rsaOID . $rsaPublicKey)),
+            $rsaOID . $rsaPublicKey
+        );
+
+        return "-----BEGIN PUBLIC KEY-----\r\n" .
+            \chunk_split(\base64_encode($rsaPublicKey), 64) .
+            '-----END PUBLIC KEY-----';
+    }
+
+    /**
+     * DER-encode the length
+     *
+     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
+     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+     *
+     * @param int $length
+     * @return string
+     */
+    private static function encodeLength(int $length): string
+    {
+        if ($length <= 0x7F) {
+            return \chr($length);
+        }
+
+        $temp = \ltrim(\pack('N', $length), \chr(0));
+
+        return \pack('Ca*', 0x80 | \strlen($temp), $temp);
+    }
+
+    /**
+     * Encodes a value into a DER object.
+     * Also defined in Firebase\JWT\JWT
+     *
+     * @param   int     $type DER tag
+     * @param   string  $value the value to encode
+     * @return  string  the encoded object
+     */
+    private static function encodeDER(int $type, string $value): string
+    {
+        $tag_header = 0;
+        if ($type === self::ASN1_SEQUENCE) {
+            $tag_header |= 0x20;
+        }
+
+        // Type
+        $der = \chr($tag_header | $type);
+
+        // Length
+        $der .= \chr(\strlen($value));
+
+        return $der . $value;
+    }
+
+    /**
+     * Encodes a string into a DER-encoded OID.
+     *
+     * @param   string $oid the OID string
+     * @return  string the binary DER-encoded OID
+     */
+    private static function encodeOID(string $oid): string
+    {
+        $octets = explode('.', $oid);
+
+        // Get the first octet
+        $first = (int) array_shift($octets);
+        $second = (int) array_shift($octets);
+        $oid = \chr($first * 40 + $second);
+
+        // Iterate over subsequent octets
+        foreach ($octets as $octet) {
+            if ($octet == 0) {
+                $oid .= \chr(0x00);
+                continue;
+            }
+            $bin = '';
+
+            while ($octet) {
+                $bin .= \chr(0x80 | ($octet & 0x7f));
+                $octet >>= 7;
+            }
+            $bin[0] = $bin[0] & \chr(0x7f);
+
+            // Convert to big endian if necessary
+            if (pack('V', 65534) == pack('L', 65534)) {
+                $oid .= strrev($bin);
+            } else {
+                $oid .= $bin;
+            }
+        }
+
+        return $oid;
+    }
+}

+ 662 - 0
api/v1/vendor/firebase/php-jwt/src/JWT.php

@@ -0,0 +1,662 @@
+<?php
+
+namespace Firebase\JWT;
+
+use ArrayAccess;
+use DateTime;
+use DomainException;
+use Exception;
+use InvalidArgumentException;
+use OpenSSLAsymmetricKey;
+use OpenSSLCertificate;
+use stdClass;
+use UnexpectedValueException;
+
+/**
+ * JSON Web Token implementation, based on this spec:
+ * https://tools.ietf.org/html/rfc7519
+ *
+ * PHP version 5
+ *
+ * @category Authentication
+ * @package  Authentication_JWT
+ * @author   Neuman Vong <neuman@twilio.com>
+ * @author   Anant Narayanan <anant@php.net>
+ * @license  http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
+ * @link     https://github.com/firebase/php-jwt
+ */
+class JWT
+{
+    private const ASN1_INTEGER = 0x02;
+    private const ASN1_SEQUENCE = 0x10;
+    private const ASN1_BIT_STRING = 0x03;
+
+    /**
+     * When checking nbf, iat or expiration times,
+     * we want to provide some extra leeway time to
+     * account for clock skew.
+     *
+     * @var int
+     */
+    public static $leeway = 0;
+
+    /**
+     * Allow the current timestamp to be specified.
+     * Useful for fixing a value within unit testing.
+     * Will default to PHP time() value if null.
+     *
+     * @var ?int
+     */
+    public static $timestamp = null;
+
+    /**
+     * @var array<string, string[]>
+     */
+    public static $supported_algs = [
+        'ES384' => ['openssl', 'SHA384'],
+        'ES256' => ['openssl', 'SHA256'],
+        'ES256K' => ['openssl', 'SHA256'],
+        'HS256' => ['hash_hmac', 'SHA256'],
+        'HS384' => ['hash_hmac', 'SHA384'],
+        'HS512' => ['hash_hmac', 'SHA512'],
+        'RS256' => ['openssl', 'SHA256'],
+        'RS384' => ['openssl', 'SHA384'],
+        'RS512' => ['openssl', 'SHA512'],
+        'EdDSA' => ['sodium_crypto', 'EdDSA'],
+    ];
+
+    /**
+     * Decodes a JWT string into a PHP object.
+     *
+     * @param string                 $jwt            The JWT
+     * @param Key|ArrayAccess<string,Key>|array<string,Key> $keyOrKeyArray  The Key or associative array of key IDs
+     *                                                                      (kid) to Key objects.
+     *                                                                      If the algorithm used is asymmetric, this is
+     *                                                                      the public key.
+     *                                                                      Each Key object contains an algorithm and
+     *                                                                      matching key.
+     *                                                                      Supported algorithms are 'ES384','ES256',
+     *                                                                      'HS256', 'HS384', 'HS512', 'RS256', 'RS384'
+     *                                                                      and 'RS512'.
+     * @param stdClass               $headers                               Optional. Populates stdClass with headers.
+     *
+     * @return stdClass The JWT's payload as a PHP object
+     *
+     * @throws InvalidArgumentException     Provided key/key-array was empty or malformed
+     * @throws DomainException              Provided JWT is malformed
+     * @throws UnexpectedValueException     Provided JWT was invalid
+     * @throws SignatureInvalidException    Provided JWT was invalid because the signature verification failed
+     * @throws BeforeValidException         Provided JWT is trying to be used before it's eligible as defined by 'nbf'
+     * @throws BeforeValidException         Provided JWT is trying to be used before it's been created as defined by 'iat'
+     * @throws ExpiredException             Provided JWT has since expired, as defined by the 'exp' claim
+     *
+     * @uses jsonDecode
+     * @uses urlsafeB64Decode
+     */
+    public static function decode(
+        string $jwt,
+        $keyOrKeyArray,
+        stdClass &$headers = null
+    ): stdClass {
+        // Validate JWT
+        $timestamp = \is_null(static::$timestamp) ? \time() : static::$timestamp;
+
+        if (empty($keyOrKeyArray)) {
+            throw new InvalidArgumentException('Key may not be empty');
+        }
+        $tks = \explode('.', $jwt);
+        if (\count($tks) !== 3) {
+            throw new UnexpectedValueException('Wrong number of segments');
+        }
+        list($headb64, $bodyb64, $cryptob64) = $tks;
+        $headerRaw = static::urlsafeB64Decode($headb64);
+        if (null === ($header = static::jsonDecode($headerRaw))) {
+            throw new UnexpectedValueException('Invalid header encoding');
+        }
+        if ($headers !== null) {
+            $headers = $header;
+        }
+        $payloadRaw = static::urlsafeB64Decode($bodyb64);
+        if (null === ($payload = static::jsonDecode($payloadRaw))) {
+            throw new UnexpectedValueException('Invalid claims encoding');
+        }
+        if (\is_array($payload)) {
+            // prevent PHP Fatal Error in edge-cases when payload is empty array
+            $payload = (object) $payload;
+        }
+        if (!$payload instanceof stdClass) {
+            throw new UnexpectedValueException('Payload must be a JSON object');
+        }
+        $sig = static::urlsafeB64Decode($cryptob64);
+        if (empty($header->alg)) {
+            throw new UnexpectedValueException('Empty algorithm');
+        }
+        if (empty(static::$supported_algs[$header->alg])) {
+            throw new UnexpectedValueException('Algorithm not supported');
+        }
+
+        $key = self::getKey($keyOrKeyArray, property_exists($header, 'kid') ? $header->kid : null);
+
+        // Check the algorithm
+        if (!self::constantTimeEquals($key->getAlgorithm(), $header->alg)) {
+            // See issue #351
+            throw new UnexpectedValueException('Incorrect key for this algorithm');
+        }
+        if (\in_array($header->alg, ['ES256', 'ES256K', 'ES384'], true)) {
+            // OpenSSL expects an ASN.1 DER sequence for ES256/ES256K/ES384 signatures
+            $sig = self::signatureToDER($sig);
+        }
+        if (!self::verify("{$headb64}.{$bodyb64}", $sig, $key->getKeyMaterial(), $header->alg)) {
+            throw new SignatureInvalidException('Signature verification failed');
+        }
+
+        // Check the nbf if it is defined. This is the time that the
+        // token can actually be used. If it's not yet that time, abort.
+        if (isset($payload->nbf) && floor($payload->nbf) > ($timestamp + static::$leeway)) {
+            throw new BeforeValidException(
+                'Cannot handle token with nbf prior to ' . \date(DateTime::ISO8601, (int) $payload->nbf)
+            );
+        }
+
+        // Check that this token has been created before 'now'. This prevents
+        // using tokens that have been created for later use (and haven't
+        // correctly used the nbf claim).
+        if (!isset($payload->nbf) && isset($payload->iat) && floor($payload->iat) > ($timestamp + static::$leeway)) {
+            throw new BeforeValidException(
+                'Cannot handle token with iat prior to ' . \date(DateTime::ISO8601, (int) $payload->iat)
+            );
+        }
+
+        // Check if this token has expired.
+        if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
+            throw new ExpiredException('Expired token');
+        }
+
+        return $payload;
+    }
+
+    /**
+     * Converts and signs a PHP array into a JWT string.
+     *
+     * @param array<mixed>          $payload PHP array
+     * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $key The secret key.
+     * @param string                $alg     Supported algorithms are 'ES384','ES256', 'ES256K', 'HS256',
+     *                                       'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
+     * @param string                $keyId
+     * @param array<string, string> $head    An array with header elements to attach
+     *
+     * @return string A signed JWT
+     *
+     * @uses jsonEncode
+     * @uses urlsafeB64Encode
+     */
+    public static function encode(
+        array $payload,
+        $key,
+        string $alg,
+        string $keyId = null,
+        array $head = null
+    ): string {
+        $header = ['typ' => 'JWT', 'alg' => $alg];
+        if ($keyId !== null) {
+            $header['kid'] = $keyId;
+        }
+        if (isset($head) && \is_array($head)) {
+            $header = \array_merge($head, $header);
+        }
+        $segments = [];
+        $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($header));
+        $segments[] = static::urlsafeB64Encode((string) static::jsonEncode($payload));
+        $signing_input = \implode('.', $segments);
+
+        $signature = static::sign($signing_input, $key, $alg);
+        $segments[] = static::urlsafeB64Encode($signature);
+
+        return \implode('.', $segments);
+    }
+
+    /**
+     * Sign a string with a given key and algorithm.
+     *
+     * @param string $msg  The message to sign
+     * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate  $key  The secret key.
+     * @param string $alg  Supported algorithms are 'EdDSA', 'ES384', 'ES256', 'ES256K', 'HS256',
+     *                    'HS384', 'HS512', 'RS256', 'RS384', and 'RS512'
+     *
+     * @return string An encrypted message
+     *
+     * @throws DomainException Unsupported algorithm or bad key was specified
+     */
+    public static function sign(
+        string $msg,
+        $key,
+        string $alg
+    ): string {
+        if (empty(static::$supported_algs[$alg])) {
+            throw new DomainException('Algorithm not supported');
+        }
+        list($function, $algorithm) = static::$supported_algs[$alg];
+        switch ($function) {
+            case 'hash_hmac':
+                if (!\is_string($key)) {
+                    throw new InvalidArgumentException('key must be a string when using hmac');
+                }
+                return \hash_hmac($algorithm, $msg, $key, true);
+            case 'openssl':
+                $signature = '';
+                $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line
+                if (!$success) {
+                    throw new DomainException('OpenSSL unable to sign data');
+                }
+                if ($alg === 'ES256' || $alg === 'ES256K') {
+                    $signature = self::signatureFromDER($signature, 256);
+                } elseif ($alg === 'ES384') {
+                    $signature = self::signatureFromDER($signature, 384);
+                }
+                return $signature;
+            case 'sodium_crypto':
+                if (!\function_exists('sodium_crypto_sign_detached')) {
+                    throw new DomainException('libsodium is not available');
+                }
+                if (!\is_string($key)) {
+                    throw new InvalidArgumentException('key must be a string when using EdDSA');
+                }
+                try {
+                    // The last non-empty line is used as the key.
+                    $lines = array_filter(explode("\n", $key));
+                    $key = base64_decode((string) end($lines));
+                    if (\strlen($key) === 0) {
+                        throw new DomainException('Key cannot be empty string');
+                    }
+                    return sodium_crypto_sign_detached($msg, $key);
+                } catch (Exception $e) {
+                    throw new DomainException($e->getMessage(), 0, $e);
+                }
+        }
+
+        throw new DomainException('Algorithm not supported');
+    }
+
+    /**
+     * Verify a signature with the message, key and method. Not all methods
+     * are symmetric, so we must have a separate verify and sign method.
+     *
+     * @param string $msg         The original message (header and body)
+     * @param string $signature   The original signature
+     * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate  $keyMaterial For Ed*, ES*, HS*, a string key works. for RS*, must be an instance of OpenSSLAsymmetricKey
+     * @param string $alg         The algorithm
+     *
+     * @return bool
+     *
+     * @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure
+     */
+    private static function verify(
+        string $msg,
+        string $signature,
+        $keyMaterial,
+        string $alg
+    ): bool {
+        if (empty(static::$supported_algs[$alg])) {
+            throw new DomainException('Algorithm not supported');
+        }
+
+        list($function, $algorithm) = static::$supported_algs[$alg];
+        switch ($function) {
+            case 'openssl':
+                $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line
+                if ($success === 1) {
+                    return true;
+                }
+                if ($success === 0) {
+                    return false;
+                }
+                // returns 1 on success, 0 on failure, -1 on error.
+                throw new DomainException(
+                    'OpenSSL error: ' . \openssl_error_string()
+                );
+            case 'sodium_crypto':
+                if (!\function_exists('sodium_crypto_sign_verify_detached')) {
+                    throw new DomainException('libsodium is not available');
+                }
+                if (!\is_string($keyMaterial)) {
+                    throw new InvalidArgumentException('key must be a string when using EdDSA');
+                }
+                try {
+                    // The last non-empty line is used as the key.
+                    $lines = array_filter(explode("\n", $keyMaterial));
+                    $key = base64_decode((string) end($lines));
+                    if (\strlen($key) === 0) {
+                        throw new DomainException('Key cannot be empty string');
+                    }
+                    if (\strlen($signature) === 0) {
+                        throw new DomainException('Signature cannot be empty string');
+                    }
+                    return sodium_crypto_sign_verify_detached($signature, $msg, $key);
+                } catch (Exception $e) {
+                    throw new DomainException($e->getMessage(), 0, $e);
+                }
+            case 'hash_hmac':
+            default:
+                if (!\is_string($keyMaterial)) {
+                    throw new InvalidArgumentException('key must be a string when using hmac');
+                }
+                $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true);
+                return self::constantTimeEquals($hash, $signature);
+        }
+    }
+
+    /**
+     * Decode a JSON string into a PHP object.
+     *
+     * @param string $input JSON string
+     *
+     * @return mixed The decoded JSON string
+     *
+     * @throws DomainException Provided string was invalid JSON
+     */
+    public static function jsonDecode(string $input)
+    {
+        $obj = \json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
+
+        if ($errno = \json_last_error()) {
+            self::handleJsonError($errno);
+        } elseif ($obj === null && $input !== 'null') {
+            throw new DomainException('Null result with non-null input');
+        }
+        return $obj;
+    }
+
+    /**
+     * Encode a PHP array into a JSON string.
+     *
+     * @param array<mixed> $input A PHP array
+     *
+     * @return string JSON representation of the PHP array
+     *
+     * @throws DomainException Provided object could not be encoded to valid JSON
+     */
+    public static function jsonEncode(array $input): string
+    {
+        if (PHP_VERSION_ID >= 50400) {
+            $json = \json_encode($input, \JSON_UNESCAPED_SLASHES);
+        } else {
+            // PHP 5.3 only
+            $json = \json_encode($input);
+        }
+        if ($errno = \json_last_error()) {
+            self::handleJsonError($errno);
+        } elseif ($json === 'null') {
+            throw new DomainException('Null result with non-null input');
+        }
+        if ($json === false) {
+            throw new DomainException('Provided object could not be encoded to valid JSON');
+        }
+        return $json;
+    }
+
+    /**
+     * Decode a string with URL-safe Base64.
+     *
+     * @param string $input A Base64 encoded string
+     *
+     * @return string A decoded string
+     *
+     * @throws InvalidArgumentException invalid base64 characters
+     */
+    public static function urlsafeB64Decode(string $input): string
+    {
+        return \base64_decode(self::convertBase64UrlToBase64($input));
+    }
+
+    /**
+     * Convert a string in the base64url (URL-safe Base64) encoding to standard base64.
+     *
+     * @param string $input A Base64 encoded string with URL-safe characters (-_ and no padding)
+     *
+     * @return string A Base64 encoded string with standard characters (+/) and padding (=), when
+     * needed.
+     *
+     * @see https://www.rfc-editor.org/rfc/rfc4648
+     */
+    public static function convertBase64UrlToBase64(string $input): string
+    {
+        $remainder = \strlen($input) % 4;
+        if ($remainder) {
+            $padlen = 4 - $remainder;
+            $input .= \str_repeat('=', $padlen);
+        }
+        return \strtr($input, '-_', '+/');
+    }
+
+    /**
+     * Encode a string with URL-safe Base64.
+     *
+     * @param string $input The string you want encoded
+     *
+     * @return string The base64 encode of what you passed in
+     */
+    public static function urlsafeB64Encode(string $input): string
+    {
+        return \str_replace('=', '', \strtr(\base64_encode($input), '+/', '-_'));
+    }
+
+
+    /**
+     * Determine if an algorithm has been provided for each Key
+     *
+     * @param Key|ArrayAccess<string,Key>|array<string,Key> $keyOrKeyArray
+     * @param string|null            $kid
+     *
+     * @throws UnexpectedValueException
+     *
+     * @return Key
+     */
+    private static function getKey(
+        $keyOrKeyArray,
+        ?string $kid
+    ): Key {
+        if ($keyOrKeyArray instanceof Key) {
+            return $keyOrKeyArray;
+        }
+
+        if (empty($kid) && $kid !== '0') {
+            throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
+        }
+
+        if ($keyOrKeyArray instanceof CachedKeySet) {
+            // Skip "isset" check, as this will automatically refresh if not set
+            return $keyOrKeyArray[$kid];
+        }
+
+        if (!isset($keyOrKeyArray[$kid])) {
+            throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
+        }
+
+        return $keyOrKeyArray[$kid];
+    }
+
+    /**
+     * @param string $left  The string of known length to compare against
+     * @param string $right The user-supplied string
+     * @return bool
+     */
+    public static function constantTimeEquals(string $left, string $right): bool
+    {
+        if (\function_exists('hash_equals')) {
+            return \hash_equals($left, $right);
+        }
+        $len = \min(self::safeStrlen($left), self::safeStrlen($right));
+
+        $status = 0;
+        for ($i = 0; $i < $len; $i++) {
+            $status |= (\ord($left[$i]) ^ \ord($right[$i]));
+        }
+        $status |= (self::safeStrlen($left) ^ self::safeStrlen($right));
+
+        return ($status === 0);
+    }
+
+    /**
+     * Helper method to create a JSON error.
+     *
+     * @param int $errno An error number from json_last_error()
+     *
+     * @throws DomainException
+     *
+     * @return void
+     */
+    private static function handleJsonError(int $errno): void
+    {
+        $messages = [
+            JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
+            JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
+            JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
+            JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
+            JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
+        ];
+        throw new DomainException(
+            isset($messages[$errno])
+            ? $messages[$errno]
+            : 'Unknown JSON error: ' . $errno
+        );
+    }
+
+    /**
+     * Get the number of bytes in cryptographic strings.
+     *
+     * @param string $str
+     *
+     * @return int
+     */
+    private static function safeStrlen(string $str): int
+    {
+        if (\function_exists('mb_strlen')) {
+            return \mb_strlen($str, '8bit');
+        }
+        return \strlen($str);
+    }
+
+    /**
+     * Convert an ECDSA signature to an ASN.1 DER sequence
+     *
+     * @param   string $sig The ECDSA signature to convert
+     * @return  string The encoded DER object
+     */
+    private static function signatureToDER(string $sig): string
+    {
+        // Separate the signature into r-value and s-value
+        $length = max(1, (int) (\strlen($sig) / 2));
+        list($r, $s) = \str_split($sig, $length);
+
+        // Trim leading zeros
+        $r = \ltrim($r, "\x00");
+        $s = \ltrim($s, "\x00");
+
+        // Convert r-value and s-value from unsigned big-endian integers to
+        // signed two's complement
+        if (\ord($r[0]) > 0x7f) {
+            $r = "\x00" . $r;
+        }
+        if (\ord($s[0]) > 0x7f) {
+            $s = "\x00" . $s;
+        }
+
+        return self::encodeDER(
+            self::ASN1_SEQUENCE,
+            self::encodeDER(self::ASN1_INTEGER, $r) .
+            self::encodeDER(self::ASN1_INTEGER, $s)
+        );
+    }
+
+    /**
+     * Encodes a value into a DER object.
+     *
+     * @param   int     $type DER tag
+     * @param   string  $value the value to encode
+     *
+     * @return  string  the encoded object
+     */
+    private static function encodeDER(int $type, string $value): string
+    {
+        $tag_header = 0;
+        if ($type === self::ASN1_SEQUENCE) {
+            $tag_header |= 0x20;
+        }
+
+        // Type
+        $der = \chr($tag_header | $type);
+
+        // Length
+        $der .= \chr(\strlen($value));
+
+        return $der . $value;
+    }
+
+    /**
+     * Encodes signature from a DER object.
+     *
+     * @param   string  $der binary signature in DER format
+     * @param   int     $keySize the number of bits in the key
+     *
+     * @return  string  the signature
+     */
+    private static function signatureFromDER(string $der, int $keySize): string
+    {
+        // OpenSSL returns the ECDSA signatures as a binary ASN.1 DER SEQUENCE
+        list($offset, $_) = self::readDER($der);
+        list($offset, $r) = self::readDER($der, $offset);
+        list($offset, $s) = self::readDER($der, $offset);
+
+        // Convert r-value and s-value from signed two's compliment to unsigned
+        // big-endian integers
+        $r = \ltrim($r, "\x00");
+        $s = \ltrim($s, "\x00");
+
+        // Pad out r and s so that they are $keySize bits long
+        $r = \str_pad($r, $keySize / 8, "\x00", STR_PAD_LEFT);
+        $s = \str_pad($s, $keySize / 8, "\x00", STR_PAD_LEFT);
+
+        return $r . $s;
+    }
+
+    /**
+     * Reads binary DER-encoded data and decodes into a single object
+     *
+     * @param string $der the binary data in DER format
+     * @param int $offset the offset of the data stream containing the object
+     * to decode
+     *
+     * @return array{int, string|null} the new offset and the decoded object
+     */
+    private static function readDER(string $der, int $offset = 0): array
+    {
+        $pos = $offset;
+        $size = \strlen($der);
+        $constructed = (\ord($der[$pos]) >> 5) & 0x01;
+        $type = \ord($der[$pos++]) & 0x1f;
+
+        // Length
+        $len = \ord($der[$pos++]);
+        if ($len & 0x80) {
+            $n = $len & 0x1f;
+            $len = 0;
+            while ($n-- && $pos < $size) {
+                $len = ($len << 8) | \ord($der[$pos++]);
+            }
+        }
+
+        // Value
+        if ($type === self::ASN1_BIT_STRING) {
+            $pos++; // Skip the first contents octet (padding indicator)
+            $data = \substr($der, $pos, $len - 1);
+            $pos += $len - 1;
+        } elseif (!$constructed) {
+            $data = \substr($der, $pos, $len);
+            $pos += $len;
+        } else {
+            $data = null;
+        }
+
+        return [$pos, $data];
+    }
+}

+ 64 - 0
api/v1/vendor/firebase/php-jwt/src/Key.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace Firebase\JWT;
+
+use InvalidArgumentException;
+use OpenSSLAsymmetricKey;
+use OpenSSLCertificate;
+use TypeError;
+
+class Key
+{
+    /** @var string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate */
+    private $keyMaterial;
+    /** @var string */
+    private $algorithm;
+
+    /**
+     * @param string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate $keyMaterial
+     * @param string $algorithm
+     */
+    public function __construct(
+        $keyMaterial,
+        string $algorithm
+    ) {
+        if (
+            !\is_string($keyMaterial)
+            && !$keyMaterial instanceof OpenSSLAsymmetricKey
+            && !$keyMaterial instanceof OpenSSLCertificate
+            && !\is_resource($keyMaterial)
+        ) {
+            throw new TypeError('Key material must be a string, resource, or OpenSSLAsymmetricKey');
+        }
+
+        if (empty($keyMaterial)) {
+            throw new InvalidArgumentException('Key material must not be empty');
+        }
+
+        if (empty($algorithm)) {
+            throw new InvalidArgumentException('Algorithm must not be empty');
+        }
+
+        // TODO: Remove in PHP 8.0 in favor of class constructor property promotion
+        $this->keyMaterial = $keyMaterial;
+        $this->algorithm = $algorithm;
+    }
+
+    /**
+     * Return the algorithm valid for this key
+     *
+     * @return string
+     */
+    public function getAlgorithm(): string
+    {
+        return $this->algorithm;
+    }
+
+    /**
+     * @return string|resource|OpenSSLAsymmetricKey|OpenSSLCertificate
+     */
+    public function getKeyMaterial()
+    {
+        return $this->keyMaterial;
+    }
+}

+ 7 - 0
api/v1/vendor/firebase/php-jwt/src/SignatureInvalidException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Firebase\JWT;
+
+class SignatureInvalidException extends \UnexpectedValueException
+{
+}

+ 76 - 0
api/v1/vendor/jean85/pretty-package-versions/.github/workflows/tests.yaml

@@ -0,0 +1,76 @@
+name: Tests
+
+on:
+  pull_request: null
+  push:
+    branches:
+      - 2.x
+
+jobs:
+  tests:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        php:
+          - '7.1'
+          - '7.2'
+          - '7.3'
+          - '7.4'
+          - '8.0'
+          - '8.1'
+        composer_version: ['v2']
+        include:
+          - description: '(prefer lowest)'
+            php: '7.1'
+            composer_version: '2.0.0'
+            dependencies: 'lowest'
+
+    name: PHP ${{ matrix.php }} tests ${{ matrix.description }}
+    steps:
+      # checkout git
+      - uses: actions/checkout@v2
+      # setup PHP
+      - uses: shivammathur/setup-php@v2
+        with:
+          php-version: ${{ matrix.php }}
+          tools: composer:${{ matrix.composer_version }}
+          coverage: xdebug
+      - uses: "ramsey/composer-install@v1"
+        with:
+          dependency-versions: ${{ matrix.dependencies }}
+      - run: vendor/bin/phpunit --coverage-clover=coverage.xml
+      - uses: codecov/codecov-action@v1
+        with:
+          file: './coverage.xml'
+          fail_ci_if_error: true
+  PHP-CS-Fixer:
+    runs-on: ubuntu-latest
+    name: Code style
+    steps:
+      - uses: actions/checkout@v2
+      - uses: shivammathur/setup-php@v2
+        with:
+          php-version: '7.4'
+          coverage: none
+      - uses: "ramsey/composer-install@v1"
+      - run: vendor/bin/php-cs-fixer fix --ansi --verbose --dry-run
+  PHPStan:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: shivammathur/setup-php@v2
+        with:
+          php-version: '7.4'
+          coverage: none
+      - uses: "ramsey/composer-install@v1"
+      - run: vendor/bin/phpstan analyse
+  Psalm:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: shivammathur/setup-php@v2
+        with:
+          php-version: '7.4'
+          coverage: none
+      - uses: "ramsey/composer-install@v1"
+      - run: vendor/bin/psalm

+ 118 - 0
api/v1/vendor/jean85/pretty-package-versions/.php_cs.dist

@@ -0,0 +1,118 @@
+<?php
+
+$config = new PhpCsFixer\Config('default');
+$config->setRiskyAllowed(true);
+$config->setRules([
+    '@PSR2' => true,
+    'align_multiline_comment' => true,
+    'array_indentation' => true,
+    'array_syntax' => [
+        'syntax' => 'short',
+    ],
+    'binary_operator_spaces' => [
+        'align_double_arrow' => false,
+        'align_equals' => false,
+    ],
+    'blank_line_after_namespace' => true,
+    'blank_line_after_opening_tag' => true,
+    'blank_line_before_statement' => [
+        'statements' => [
+            'return',
+        ],
+    ],
+    'cast_spaces' => [
+        'space' => 'single',
+    ],
+    'class_attributes_separation' => [
+        'elements' => [
+            'method' => 'one', 
+            'property' => 'one',
+        ],
+    ],
+    'compact_nullable_typehint' => true,
+    'concat_space' => [
+        'spacing' => 'one',
+    ],
+    'declare_equal_normalize' => true,
+    'declare_strict_types' => true,
+    'function_typehint_space' => true,
+    'include' => true,
+    'indentation_type' => true,
+    'lowercase_cast' => true,
+    'method_chaining_indentation' => true,
+    'method_separation' => true,
+    'multiline_comment_opening_closing' => true,
+    'native_function_casing' => true,
+    'new_with_braces' => true,
+    'no_blank_lines_after_phpdoc' => true,
+    'no_empty_comment' => true,
+    'no_empty_phpdoc' => true,
+    'no_empty_statement' => true,
+    'no_extra_consecutive_blank_lines' => [
+        'tokens' => [
+            'curly_brace_block',
+            'extra',
+            'parenthesis_brace_block',
+            'square_brace_block',
+            'throw',
+            'use',
+        ],
+    ],
+    'no_leading_import_slash' => true,
+    'no_leading_namespace_whitespace' => true,
+    'no_mixed_echo_print' => [
+        'use' => 'echo',
+    ],
+    'no_multiline_whitespace_around_double_arrow' => true,
+    'no_short_bool_cast' => true,
+    'no_singleline_whitespace_before_semicolons' => true,
+    'no_spaces_around_offset' => true,
+    'no_superfluous_phpdoc_tags' => [
+        'allow_mixed' => true,
+    ],
+    'no_trailing_comma_in_list_call' => true,
+    'no_trailing_comma_in_singleline_array' => true,
+    'no_unneeded_control_parentheses' => true,
+    'no_unused_imports' => true,
+    'no_whitespace_before_comma_in_array' => true,
+    'no_whitespace_in_blank_line' => true,
+    'not_operator_with_successor_space' => true,
+    'normalize_index_brace' => true,
+    'object_operator_without_whitespace' => true,
+    'ordered_imports' => true,
+    'phpdoc_align' => false,
+    'phpdoc_annotation_without_dot' => true,
+    'phpdoc_indent' => true,
+    'phpdoc_no_package' => true,
+    'phpdoc_no_useless_inheritdoc' => true,
+    'phpdoc_order' => true,
+    'phpdoc_scalar' => true,
+    'phpdoc_separation' => true,
+    'phpdoc_single_line_var_spacing' => true,
+    'phpdoc_to_comment' => true,
+    'phpdoc_trim' => true,
+    'phpdoc_trim_consecutive_blank_line_separation' => true,
+    'phpdoc_types' => true,
+    'phpdoc_var_without_name' => true,
+    'pre_increment' => true,
+    'psr4' => true,
+    'return_type_declaration' => true,
+    'short_scalar_cast' => true,
+    'single_blank_line_before_namespace' => true,
+    'single_class_element_per_statement' => true,
+    'single_quote' => true,
+    'space_after_semicolon' => true,
+    'standardize_not_equals' => true,
+    'ternary_operator_spaces' => true,
+    'ternary_to_null_coalescing' => true,
+    'trailing_comma_in_multiline_array' => true,
+    'trim_array_spaces' => true,
+    'unary_operator_spaces' => true,
+    'whitespace_after_comma_in_array' => true,
+]);
+
+$finder = PhpCsFixer\Finder::create();
+$finder->in(__DIR__);
+$config->setFinder($finder);
+
+return $config;

+ 21 - 0
api/v1/vendor/jean85/pretty-package-versions/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Alessandro Lai
+
+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.

+ 19 - 0
api/v1/vendor/jean85/pretty-package-versions/Makefile

@@ -0,0 +1,19 @@
+pre-commit-check: composer cs phpstan psalm test
+
+composer:
+	composer install
+
+cs:
+	vendor/bin/php-cs-fixer fix --verbose
+
+cs-dry-run:
+	vendor/bin/php-cs-fixer fix --verbose --dry-run
+
+phpstan:
+	vendor/bin/phpstan analyze
+
+psalm:
+	vendor/bin/psalm
+
+test:
+	vendor/bin/phpunit

+ 1 - 0
api/v1/vendor/jean85/pretty-package-versions/codecov.yml

@@ -0,0 +1 @@
+comment: false

+ 50 - 0
api/v1/vendor/jean85/pretty-package-versions/composer.json

@@ -0,0 +1,50 @@
+{
+    "name": "jean85/pretty-package-versions",
+    "description": "A library to get pretty versions strings of installed dependencies",
+    "type": "library",
+    "require": {
+        "php": "^7.1|^8.0",
+        "composer-runtime-api": "^2.0.0"
+    },
+    "require-dev": {
+        "friendsofphp/php-cs-fixer": "^2.17",
+        "jean85/composer-provided-replaced-stub-package": "^1.0",
+        "phpstan/phpstan": "^0.12.66",
+        "phpunit/phpunit": "^7.5|^8.5|^9.4",
+        "vimeo/psalm": "^4.3"
+    },
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Alessandro Lai",
+            "email": "alessandro.lai85@gmail.com"
+        }
+    ],
+    "support": {
+        "issues": "https://github.com/Jean85/pretty-package-versions/issues"
+    },
+    "keywords": [
+        "package",
+        "versions",
+        "composer",
+        "release"
+    ],
+    "config": {
+        "sort-packages": true
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.x-dev"
+        }
+    },
+    "autoload": {
+        "psr-4": {
+            "Jean85\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Tests\\": "tests"
+        }
+    }
+}

+ 5 - 0
api/v1/vendor/jean85/pretty-package-versions/phpstan.neon

@@ -0,0 +1,5 @@
+parameters:
+    level: 8
+    paths:
+        - src/
+        - tests/

+ 16 - 0
api/v1/vendor/jean85/pretty-package-versions/psalm.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<psalm
+    errorLevel="4"
+    resolveFromConfigFile="true"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="https://getpsalm.org/schema/config"
+    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
+>
+    <projectFiles>
+        <directory name="src" />
+        <directory name="tests" />
+        <ignoreFiles>
+            <directory name="vendor" />
+        </ignoreFiles>
+    </projectFiles>
+</psalm>

+ 13 - 0
api/v1/vendor/jean85/pretty-package-versions/src/Exception/ProvidedPackageException.php

@@ -0,0 +1,13 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Jean85\Exception;
+
+class ProvidedPackageException extends \Exception implements VersionMissingExceptionInterface
+{
+    public static function create(string $packageName): VersionMissingExceptionInterface
+    {
+        return new self('Cannot retrieve a version for package ' . $packageName . ' since it is provided, probably a metapackage');
+    }
+}

+ 13 - 0
api/v1/vendor/jean85/pretty-package-versions/src/Exception/ReplacedPackageException.php

@@ -0,0 +1,13 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Jean85\Exception;
+
+class ReplacedPackageException extends \Exception implements VersionMissingExceptionInterface
+{
+    public static function create(string $packageName): VersionMissingExceptionInterface
+    {
+        return new self('Cannot retrieve a version for package ' . $packageName . ' since it is replaced by some other package');
+    }
+}

+ 10 - 0
api/v1/vendor/jean85/pretty-package-versions/src/Exception/VersionMissingExceptionInterface.php

@@ -0,0 +1,10 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Jean85\Exception;
+
+interface VersionMissingExceptionInterface extends \Throwable
+{
+    public static function create(string $packageName): self;
+}

+ 77 - 0
api/v1/vendor/jean85/pretty-package-versions/src/PrettyVersions.php

@@ -0,0 +1,77 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Jean85;
+
+use Composer\InstalledVersions;
+use Jean85\Exception\ProvidedPackageException;
+use Jean85\Exception\ReplacedPackageException;
+use Jean85\Exception\VersionMissingExceptionInterface;
+
+class PrettyVersions
+{
+    /**
+     * @throws VersionMissingExceptionInterface When a package is provided ({@see ProvidedPackageException}) or replaced ({@see ReplacedPackageException})
+     */
+    public static function getVersion(string $packageName): Version
+    {
+        self::checkProvidedPackages($packageName);
+
+        self::checkReplacedPackages($packageName);
+
+        return new Version(
+            $packageName,
+            InstalledVersions::getPrettyVersion($packageName),
+            InstalledVersions::getReference($packageName)
+        );
+    }
+
+    public static function getRootPackageName(): string
+    {
+        return InstalledVersions::getRootPackage()['name'];
+    }
+
+    public static function getRootPackageVersion(): Version
+    {
+        return new Version(
+            self::getRootPackageName(),
+            InstalledVersions::getRootPackage()['pretty_version'],
+            InstalledVersions::getRootPackage()['reference']
+        );
+    }
+
+    protected static function checkProvidedPackages(string $packageName): void
+    {
+        if (! method_exists(InstalledVersions::class, 'getAllRawData')) {
+            if (isset(InstalledVersions::getRawData()['versions'][$packageName]['provided'])) {
+                throw ProvidedPackageException::create($packageName);
+            }
+
+            return;
+        }
+
+        foreach (InstalledVersions::getAllRawData() as $installed) {
+            if (isset($installed['versions'][$packageName]['provided'])) {
+                throw ProvidedPackageException::create($packageName);
+            }
+        }
+    }
+
+    protected static function checkReplacedPackages(string $packageName): void
+    {
+        if (! method_exists(InstalledVersions::class, 'getAllRawData')) {
+            if (isset(InstalledVersions::getRawData()['versions'][$packageName]['replaced'])) {
+                throw ReplacedPackageException::create($packageName);
+            }
+
+            return;
+        }
+
+        foreach (InstalledVersions::getAllRawData() as $installed) {
+            if (isset($installed['versions'][$packageName]['replaced'])) {
+                throw ReplacedPackageException::create($packageName);
+            }
+        }
+    }
+}

+ 101 - 0
api/v1/vendor/jean85/pretty-package-versions/src/Version.php

@@ -0,0 +1,101 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Jean85;
+
+class Version
+{
+    private const SHORT_COMMIT_LENGTH = 7;
+
+    /** @var string */
+    private $packageName;
+
+    /** @var string */
+    private $prettyVersion;
+
+    /** @var string */
+    private $reference;
+
+    /** @var bool */
+    private $versionIsTagged;
+
+    public const NO_VERSION_TEXT = '{no version}';
+    public const NO_REFERENCE_TEXT = '{no reference}';
+
+    public function __construct(string $packageName, ?string $prettyVersion = null, ?string $reference = null)
+    {
+        $this->packageName = $packageName;
+        $this->prettyVersion = $prettyVersion ?? self::NO_VERSION_TEXT;
+        $this->reference = $reference ?? self::NO_REFERENCE_TEXT;
+        $this->versionIsTagged = preg_match('/[^v\d.]/', $this->getShortVersion()) === 0;
+    }
+
+    public function getPrettyVersion(): string
+    {
+        if ($this->versionIsTagged) {
+            return $this->prettyVersion;
+        }
+
+        return $this->getVersionWithShortReference();
+    }
+
+    public function getFullVersion(): string
+    {
+        return $this->prettyVersion . '@' . $this->getReference();
+    }
+
+    /**
+     * @deprecated
+     */
+    public function getVersionWithShortCommit(): string
+    {
+        return $this->getVersionWithShortReference();
+    }
+
+    public function getVersionWithShortReference(): string
+    {
+        return $this->prettyVersion . '@' . $this->getShortReference();
+    }
+
+    public function getPackageName(): string
+    {
+        return $this->packageName;
+    }
+
+    public function getShortVersion(): string
+    {
+        return $this->prettyVersion;
+    }
+
+    /**
+     * @deprecated
+     */
+    public function getCommitHash(): string
+    {
+        return $this->getReference();
+    }
+
+    public function getReference(): string
+    {
+        return $this->reference;
+    }
+
+    /**
+     * @deprecated
+     */
+    public function getShortCommitHash(): string
+    {
+        return $this->getShortReference();
+    }
+
+    public function getShortReference(): string
+    {
+        return substr($this->reference, 0, self::SHORT_COMMIT_LENGTH);
+    }
+
+    public function __toString(): string
+    {
+        return $this->getPrettyVersion();
+    }
+}

+ 202 - 0
api/v1/vendor/mongodb/mongodb/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 47 - 0
api/v1/vendor/mongodb/mongodb/composer.json

@@ -0,0 +1,47 @@
+{
+    "name": "mongodb/mongodb",
+    "description": "MongoDB driver library",
+    "keywords": ["database", "driver", "mongodb", "persistence"],
+    "homepage": "https://jira.mongodb.org/browse/PHPLIB",
+    "license": "Apache-2.0",
+    "authors": [
+        { "name": "Andreas Braun", "email": "andreas.braun@mongodb.com" },
+        { "name": "Jeremy Mikola", "email": "jmikola@gmail.com" }
+    ],
+    "require": {
+        "php": "^7.2 || ^8.0",
+        "ext-hash": "*",
+        "ext-json": "*",
+        "ext-mongodb": "^1.16.0",
+        "jean85/pretty-package-versions": "^2.0.1",
+        "symfony/polyfill-php73": "^1.27",
+        "symfony/polyfill-php80": "^1.27",
+        "symfony/polyfill-php81": "^1.27"
+    },
+    "require-dev": {
+        "doctrine/coding-standard": "^11.1",
+        "rector/rector": "^0.16.0",
+        "squizlabs/php_codesniffer": "^3.7",
+        "symfony/phpunit-bridge": "^5.2",
+        "vimeo/psalm": "^4.28"
+    },
+    "autoload": {
+        "psr-4": { "MongoDB\\": "src/" },
+        "files": [ "src/functions.php" ]
+    },
+    "autoload-dev": {
+        "psr-4": { "MongoDB\\Tests\\": "tests/" },
+        "files": [ "tests/PHPUnit/Functions.php" ]
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "1.16.x-dev"
+        }
+    },
+    "config": {
+        "allow-plugins": {
+            "dealerdirect/phpcodesniffer-composer-installer": true
+        },
+        "sort-packages": true
+    }
+}

+ 33 - 0
api/v1/vendor/mongodb/mongodb/rector.php

@@ -0,0 +1,33 @@
+<?php
+
+use Rector\Config\RectorConfig;
+use Rector\DeadCode\Rector\ClassLike\RemoveAnnotationRector;
+use Rector\Php56\Rector\FunctionLike\AddDefaultValueForUndefinedVariableRector;
+use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector;
+use Rector\Set\ValueObject\LevelSetList;
+
+return static function (RectorConfig $rectorConfig): void {
+    $rectorConfig->paths([
+        __DIR__ . '/examples',
+        __DIR__ . '/src',
+        __DIR__ . '/tests',
+        __DIR__ . '/tools',
+    ]);
+
+    // Modernize code
+    $rectorConfig->sets([LevelSetList::UP_TO_PHP_72]);
+
+    $rectorConfig->skip([
+        // Falsely detect unassigned variables in code paths stopped by PHPUnit\Framework\Assert::markTestSkipped()
+        AddDefaultValueForUndefinedVariableRector::class => [
+            __DIR__ . '/tests/',
+        ],
+        // @see https://github.com/phpstan/phpstan-src/pull/2429
+        RemoveExtraParametersRector::class => [
+            __DIR__ . '/src/Operation/',
+        ],
+    ]);
+
+    // All classes are public API by default, unless marked with @internal.
+    $rectorConfig->ruleWithConfiguration(RemoveAnnotationRector::class, ['api']);
+};

+ 188 - 0
api/v1/vendor/mongodb/mongodb/src/BulkWriteResult.php

@@ -0,0 +1,188 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\WriteResult;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Result class for a bulk write operation.
+ */
+class BulkWriteResult
+{
+    /** @var WriteResult */
+    private $writeResult;
+
+    /** @var array */
+    private $insertedIds;
+
+    /** @var boolean */
+    private $isAcknowledged;
+
+    public function __construct(WriteResult $writeResult, array $insertedIds)
+    {
+        $this->writeResult = $writeResult;
+        $this->insertedIds = $insertedIds;
+        $this->isAcknowledged = $writeResult->isAcknowledged();
+    }
+
+    /**
+     * Return the number of documents that were deleted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getDeletedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getDeletedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return the number of documents that were inserted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getInsertedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getInsertedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return a map of the inserted documents' IDs.
+     *
+     * The index of each ID in the map corresponds to each document's position
+     * in the bulk operation. If a document had an ID prior to inserting (i.e.
+     * the driver did not generate an ID), the index will contain its "_id"
+     * field value. Any driver-generated ID will be a MongoDB\BSON\ObjectId
+     * instance.
+     *
+     * @return array
+     */
+    public function getInsertedIds()
+    {
+        return $this->insertedIds;
+    }
+
+    /**
+     * Return the number of documents that were matched by the filter.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getMatchedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getMatchedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return the number of documents that were modified.
+     *
+     * This value is undefined (i.e. null) if the write executed as a legacy
+     * operation instead of command.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getModifiedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getModifiedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return the number of documents that were upserted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getUpsertedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getUpsertedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return a map of the upserted documents' IDs.
+     *
+     * The index of each ID in the map corresponds to each document's position
+     * in bulk operation. If a document had an ID prior to upserting (i.e. the
+     * server did not need to generate an ID), this will contain its "_id". Any
+     * server-generated ID will be a MongoDB\BSON\ObjectId instance.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see BulkWriteResult::isAcknowledged()
+     * @return array
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getUpsertedIds()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getUpsertedIds();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return whether this update was acknowledged by the server.
+     *
+     * If the update was not acknowledged, other fields from the WriteResult
+     * (e.g. matchedCount) will be undefined.
+     *
+     * @return boolean
+     */
+    public function isAcknowledged()
+    {
+        return $this->isAcknowledged;
+    }
+}

+ 276 - 0
api/v1/vendor/mongodb/mongodb/src/ChangeStream.php

@@ -0,0 +1,276 @@
+<?php
+/*
+ * Copyright 2017-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use Iterator;
+use MongoDB\Driver\CursorId;
+use MongoDB\Driver\Exception\ConnectionException;
+use MongoDB\Driver\Exception\RuntimeException;
+use MongoDB\Driver\Exception\ServerException;
+use MongoDB\Exception\BadMethodCallException;
+use MongoDB\Exception\ResumeTokenException;
+use MongoDB\Model\ChangeStreamIterator;
+use ReturnTypeWillChange;
+
+use function call_user_func;
+use function in_array;
+
+/**
+ * Iterator for a change stream.
+ *
+ * @see \MongoDB\Collection::watch()
+ * @see https://mongodb.com/docs/manual/reference/method/db.watch/#mongodb-method-db.watch
+ *
+ * @psalm-type ResumeCallable = callable(array|object|null, bool): ChangeStreamIterator
+ */
+class ChangeStream implements Iterator
+{
+    /**
+     * @deprecated 1.4
+     * @todo make this constant private in 2.0 (see: PHPLIB-360)
+     */
+    public const CURSOR_NOT_FOUND = 43;
+
+    private const RESUMABLE_ERROR_CODES = [
+        6, // HostUnreachable
+        7, // HostNotFound
+        89, // NetworkTimeout
+        91, // ShutdownInProgress
+        189, // PrimarySteppedDown
+        262, // ExceededTimeLimit
+        9001, // SocketException
+        10107, // NotPrimary
+        11600, // InterruptedAtShutdown
+        11602, // InterruptedDueToReplStateChange
+        13435, // NotPrimaryNoSecondaryOk
+        13436, // NotPrimaryOrSecondary
+        63, // StaleShardVersion
+        150, // StaleEpoch
+        13388, // StaleConfig
+        234, // RetryChangeStream
+        133, // FailedToSatisfyReadPreference
+    ];
+
+    private const WIRE_VERSION_FOR_RESUMABLE_CHANGE_STREAM_ERROR = 9;
+
+    /** @var ResumeCallable|null */
+    private $resumeCallable;
+
+    /** @var ChangeStreamIterator */
+    private $iterator;
+
+    /** @var integer */
+    private $key = 0;
+
+    /**
+     * Whether the change stream has advanced to its first result. This is used
+     * to determine whether $key should be incremented after an iteration event.
+     *
+     * @var boolean
+     */
+    private $hasAdvanced = false;
+
+    /**
+     * @internal
+     *
+     * @param ResumeCallable $resumeCallable
+     */
+    public function __construct(ChangeStreamIterator $iterator, callable $resumeCallable)
+    {
+        $this->iterator = $iterator;
+        $this->resumeCallable = $resumeCallable;
+    }
+
+    /**
+     * @see https://php.net/iterator.current
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function current()
+    {
+        return $this->iterator->current();
+    }
+
+    /** @return CursorId */
+    public function getCursorId()
+    {
+        return $this->iterator->getInnerIterator()->getId();
+    }
+
+    /**
+     * Returns the resume token for the iterator's current position.
+     *
+     * Null may be returned if no change documents have been iterated and the
+     * server did not include a postBatchResumeToken in its aggregate or getMore
+     * command response.
+     *
+     * @return array|object|null
+     */
+    public function getResumeToken()
+    {
+        return $this->iterator->getResumeToken();
+    }
+
+    /**
+     * @see https://php.net/iterator.key
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function key()
+    {
+        if ($this->valid()) {
+            return $this->key;
+        }
+
+        return null;
+    }
+
+    /**
+     * @see https://php.net/iterator.next
+     * @return void
+     * @throws ResumeTokenException
+     */
+    #[ReturnTypeWillChange]
+    public function next()
+    {
+        try {
+            $this->iterator->next();
+            $this->onIteration($this->hasAdvanced);
+        } catch (RuntimeException $e) {
+            $this->resumeOrThrow($e);
+        }
+    }
+
+    /**
+     * @see https://php.net/iterator.rewind
+     * @return void
+     * @throws ResumeTokenException
+     */
+    #[ReturnTypeWillChange]
+    public function rewind()
+    {
+        try {
+            $this->iterator->rewind();
+            /* Unlike next() and resume(), the decision to increment the key
+             * does not depend on whether the change stream has advanced. This
+             * ensures that multiple calls to rewind() do not alter state. */
+            $this->onIteration(false);
+        } catch (RuntimeException $e) {
+            $this->resumeOrThrow($e);
+        }
+    }
+
+    /**
+     * @see https://php.net/iterator.valid
+     * @return boolean
+     */
+    #[ReturnTypeWillChange]
+    public function valid()
+    {
+        return $this->iterator->valid();
+    }
+
+    /**
+     * Determines if an exception is a resumable error.
+     *
+     * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resumable-error
+     */
+    private function isResumableError(RuntimeException $exception): bool
+    {
+        if ($exception instanceof ConnectionException) {
+            return true;
+        }
+
+        if (! $exception instanceof ServerException) {
+            return false;
+        }
+
+        if ($exception->getCode() === self::CURSOR_NOT_FOUND) {
+            return true;
+        }
+
+        if (server_supports_feature($this->iterator->getServer(), self::WIRE_VERSION_FOR_RESUMABLE_CHANGE_STREAM_ERROR)) {
+            return $exception->hasErrorLabel('ResumableChangeStreamError');
+        }
+
+        return in_array($exception->getCode(), self::RESUMABLE_ERROR_CODES);
+    }
+
+    /**
+     * Perform housekeeping after an iteration event.
+     *
+     * @param boolean $incrementKey Increment $key if there is a current result
+     * @throws ResumeTokenException
+     */
+    private function onIteration(bool $incrementKey): void
+    {
+        /* If the cursorId is 0, the server has invalidated the cursor and we
+         * will never perform another getMore nor need to resume since any
+         * remaining results (up to and including the invalidate event) will
+         * have been received in the last response. Therefore, we can unset the
+         * resumeCallable. This will free any reference to Watch as well as the
+         * only reference to any implicit session created therein. */
+        if ((string) $this->getCursorId() === '0') {
+            $this->resumeCallable = null;
+        }
+
+        /* Return early if there is not a current result. Avoid any attempt to
+         * increment the iterator's key. */
+        if (! $this->valid()) {
+            return;
+        }
+
+        if ($incrementKey) {
+            $this->key++;
+        }
+
+        $this->hasAdvanced = true;
+    }
+
+    /**
+     * Recreates the ChangeStreamIterator after a resumable server error.
+     */
+    private function resume(): void
+    {
+        if (! $this->resumeCallable) {
+            throw new BadMethodCallException('Cannot resume a closed change stream.');
+        }
+
+        $this->iterator = call_user_func($this->resumeCallable, $this->getResumeToken(), $this->hasAdvanced);
+
+        $this->iterator->rewind();
+
+        $this->onIteration($this->hasAdvanced);
+    }
+
+    /**
+     * Either resumes after a resumable error or re-throws the exception.
+     *
+     * @throws RuntimeException
+     */
+    private function resumeOrThrow(RuntimeException $exception): void
+    {
+        if ($this->isResumableError($exception)) {
+            $this->resume();
+
+            return;
+        }
+
+        throw $exception;
+    }
+}

+ 423 - 0
api/v1/vendor/mongodb/mongodb/src/Client.php

@@ -0,0 +1,423 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use Iterator;
+use Jean85\PrettyVersions;
+use MongoDB\Driver\ClientEncryption;
+use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\Model\BSONArray;
+use MongoDB\Model\BSONDocument;
+use MongoDB\Model\DatabaseInfoIterator;
+use MongoDB\Operation\DropDatabase;
+use MongoDB\Operation\ListDatabaseNames;
+use MongoDB\Operation\ListDatabases;
+use MongoDB\Operation\Watch;
+use Throwable;
+
+use function is_array;
+use function is_string;
+
+class Client
+{
+    public const DEFAULT_URI = 'mongodb://127.0.0.1/';
+
+    private const DEFAULT_TYPE_MAP = [
+        'array' => BSONArray::class,
+        'document' => BSONDocument::class,
+        'root' => BSONDocument::class,
+    ];
+
+    private const HANDSHAKE_SEPARATOR = '/';
+
+    /** @var string|null */
+    private static $version;
+
+    /** @var Manager */
+    private $manager;
+
+    /** @var ReadConcern */
+    private $readConcern;
+
+    /** @var ReadPreference */
+    private $readPreference;
+
+    /** @var string */
+    private $uri;
+
+    /** @var array */
+    private $typeMap;
+
+    /** @var WriteConcern */
+    private $writeConcern;
+
+    /**
+     * Constructs a new Client instance.
+     *
+     * This is the preferred class for connecting to a MongoDB server or
+     * cluster of servers. It serves as a gateway for accessing individual
+     * databases and collections.
+     *
+     * Supported driver-specific options:
+     *
+     *  * typeMap (array): Default type map for cursors and BSON documents.
+     *
+     * Other options are documented in MongoDB\Driver\Manager::__construct().
+     *
+     * @see https://mongodb.com/docs/manual/reference/connection-string/
+     * @see https://php.net/manual/en/mongodb-driver-manager.construct.php
+     * @see https://php.net/manual/en/mongodb.persistence.php#mongodb.persistence.typemaps
+     * @param string|null $uri           MongoDB connection string. If none is provided, this defaults to self::DEFAULT_URI.
+     * @param array       $uriOptions    Additional connection string options
+     * @param array       $driverOptions Driver-specific options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverInvalidArgumentException for parameter/option parsing errors in the driver
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function __construct(?string $uri = null, array $uriOptions = [], array $driverOptions = [])
+    {
+        $driverOptions += ['typeMap' => self::DEFAULT_TYPE_MAP];
+
+        if (! is_array($driverOptions['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" driver option', $driverOptions['typeMap'], 'array');
+        }
+
+        if (isset($driverOptions['autoEncryption']['keyVaultClient'])) {
+            if ($driverOptions['autoEncryption']['keyVaultClient'] instanceof self) {
+                $driverOptions['autoEncryption']['keyVaultClient'] = $driverOptions['autoEncryption']['keyVaultClient']->manager;
+            } elseif (! $driverOptions['autoEncryption']['keyVaultClient'] instanceof Manager) {
+                throw InvalidArgumentException::invalidType('"keyVaultClient" autoEncryption option', $driverOptions['autoEncryption']['keyVaultClient'], [self::class, Manager::class]);
+            }
+        }
+
+        $driverOptions['driver'] = $this->mergeDriverInfo($driverOptions['driver'] ?? []);
+
+        $this->uri = $uri ?? self::DEFAULT_URI;
+        $this->typeMap = $driverOptions['typeMap'];
+
+        unset($driverOptions['typeMap']);
+
+        $this->manager = new Manager($uri, $uriOptions, $driverOptions);
+        $this->readConcern = $this->manager->getReadConcern();
+        $this->readPreference = $this->manager->getReadPreference();
+        $this->writeConcern = $this->manager->getWriteConcern();
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'manager' => $this->manager,
+            'uri' => $this->uri,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+    }
+
+    /**
+     * Select a database.
+     *
+     * Note: databases whose names contain special characters (e.g. "-") may
+     * be selected with complex syntax (e.g. $client->{"that-database"}) or
+     * {@link selectDatabase()}.
+     *
+     * @see https://php.net/oop5.overloading#object.get
+     * @see https://php.net/types.string#language.types.string.parsing.complex
+     * @param string $databaseName Name of the database to select
+     * @return Database
+     */
+    public function __get(string $databaseName)
+    {
+        return $this->selectDatabase($databaseName);
+    }
+
+    /**
+     * Return the connection string (i.e. URI).
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->uri;
+    }
+
+    /**
+     * Returns a ClientEncryption instance for explicit encryption and decryption
+     *
+     * @param array $options Encryption options
+     *
+     * @return ClientEncryption
+     */
+    public function createClientEncryption(array $options)
+    {
+        if (isset($options['keyVaultClient'])) {
+            if ($options['keyVaultClient'] instanceof self) {
+                $options['keyVaultClient'] = $options['keyVaultClient']->manager;
+            } elseif (! $options['keyVaultClient'] instanceof Manager) {
+                throw InvalidArgumentException::invalidType('"keyVaultClient" option', $options['keyVaultClient'], [self::class, Manager::class]);
+            }
+        }
+
+        return $this->manager->createClientEncryption($options);
+    }
+
+    /**
+     * Drop a database.
+     *
+     * @see DropDatabase::__construct() for supported options
+     * @param string $databaseName Database name
+     * @param array  $options      Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are unsupported on the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function dropDatabase(string $databaseName, array $options = [])
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropDatabase($databaseName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Return the Manager.
+     *
+     * @return Manager
+     */
+    public function getManager()
+    {
+        return $this->manager;
+    }
+
+    /**
+     * Return the read concern for this client.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
+     * @return ReadConcern
+     */
+    public function getReadConcern()
+    {
+        return $this->readConcern;
+    }
+
+    /**
+     * Return the read preference for this client.
+     *
+     * @return ReadPreference
+     */
+    public function getReadPreference()
+    {
+        return $this->readPreference;
+    }
+
+    /**
+     * Return the type map for this client.
+     *
+     * @return array
+     */
+    public function getTypeMap()
+    {
+        return $this->typeMap;
+    }
+
+    /**
+     * Return the write concern for this client.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
+     * @return WriteConcern
+     */
+    public function getWriteConcern()
+    {
+        return $this->writeConcern;
+    }
+
+    /**
+     * List database names.
+     *
+     * @see ListDatabaseNames::__construct() for supported options
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function listDatabaseNames(array $options = []): Iterator
+    {
+        $operation = new ListDatabaseNames($options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * List databases.
+     *
+     * @see ListDatabases::__construct() for supported options
+     * @return DatabaseInfoIterator
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function listDatabases(array $options = [])
+    {
+        $operation = new ListDatabases($options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Select a collection.
+     *
+     * @see Collection::__construct() for supported options
+     * @param string $databaseName   Name of the database containing the collection
+     * @param string $collectionName Name of the collection to select
+     * @param array  $options        Collection constructor options
+     * @return Collection
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function selectCollection(string $databaseName, string $collectionName, array $options = [])
+    {
+        $options += ['typeMap' => $this->typeMap];
+
+        return new Collection($this->manager, $databaseName, $collectionName, $options);
+    }
+
+    /**
+     * Select a database.
+     *
+     * @see Database::__construct() for supported options
+     * @param string $databaseName Name of the database to select
+     * @param array  $options      Database constructor options
+     * @return Database
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function selectDatabase(string $databaseName, array $options = [])
+    {
+        $options += ['typeMap' => $this->typeMap];
+
+        return new Database($this->manager, $databaseName, $options);
+    }
+
+    /**
+     * Start a new client session.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-manager.startsession.php
+     * @param array $options Session options
+     * @return Session
+     */
+    public function startSession(array $options = [])
+    {
+        return $this->manager->startSession($options);
+    }
+
+    /**
+     * Create a change stream for watching changes to the cluster.
+     *
+     * @see Watch::__construct() for supported options
+     * @param array $pipeline Aggregation pipeline
+     * @param array $options  Command options
+     * @return ChangeStream
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function watch(array $pipeline = [], array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new Watch($this->manager, null, null, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+
+    private static function getVersion(): string
+    {
+        if (self::$version === null) {
+            try {
+                self::$version = PrettyVersions::getVersion('mongodb/mongodb')->getPrettyVersion();
+            } catch (Throwable $t) {
+                return 'unknown';
+            }
+        }
+
+        return self::$version;
+    }
+
+    private function mergeDriverInfo(array $driver): array
+    {
+        $mergedDriver = [
+            'name' => 'PHPLIB',
+            'version' => self::getVersion(),
+        ];
+
+        if (isset($driver['name'])) {
+            if (! is_string($driver['name'])) {
+                throw InvalidArgumentException::invalidType('"name" handshake option', $driver['name'], 'string');
+            }
+
+            $mergedDriver['name'] .= self::HANDSHAKE_SEPARATOR . $driver['name'];
+        }
+
+        if (isset($driver['version'])) {
+            if (! is_string($driver['version'])) {
+                throw InvalidArgumentException::invalidType('"version" handshake option', $driver['version'], 'string');
+            }
+
+            $mergedDriver['version'] .= self::HANDSHAKE_SEPARATOR . $driver['version'];
+        }
+
+        if (isset($driver['platform'])) {
+            $mergedDriver['platform'] = $driver['platform'];
+        }
+
+        return $mergedDriver;
+    }
+}

+ 1160 - 0
api/v1/vendor/mongodb/mongodb/src/Collection.php

@@ -0,0 +1,1160 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\BSON\JavascriptInterface;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\Model\BSONArray;
+use MongoDB\Model\BSONDocument;
+use MongoDB\Model\IndexInfo;
+use MongoDB\Model\IndexInfoIterator;
+use MongoDB\Operation\Aggregate;
+use MongoDB\Operation\BulkWrite;
+use MongoDB\Operation\Count;
+use MongoDB\Operation\CountDocuments;
+use MongoDB\Operation\CreateIndexes;
+use MongoDB\Operation\DeleteMany;
+use MongoDB\Operation\DeleteOne;
+use MongoDB\Operation\Distinct;
+use MongoDB\Operation\DropCollection;
+use MongoDB\Operation\DropEncryptedCollection;
+use MongoDB\Operation\DropIndexes;
+use MongoDB\Operation\EstimatedDocumentCount;
+use MongoDB\Operation\Explain;
+use MongoDB\Operation\Explainable;
+use MongoDB\Operation\Find;
+use MongoDB\Operation\FindOne;
+use MongoDB\Operation\FindOneAndDelete;
+use MongoDB\Operation\FindOneAndReplace;
+use MongoDB\Operation\FindOneAndUpdate;
+use MongoDB\Operation\InsertMany;
+use MongoDB\Operation\InsertOne;
+use MongoDB\Operation\ListIndexes;
+use MongoDB\Operation\MapReduce;
+use MongoDB\Operation\RenameCollection;
+use MongoDB\Operation\ReplaceOne;
+use MongoDB\Operation\UpdateMany;
+use MongoDB\Operation\UpdateOne;
+use MongoDB\Operation\Watch;
+use Traversable;
+
+use function array_diff_key;
+use function array_intersect_key;
+use function current;
+use function is_array;
+use function strlen;
+
+class Collection
+{
+    private const DEFAULT_TYPE_MAP = [
+        'array' => BSONArray::class,
+        'document' => BSONDocument::class,
+        'root' => BSONDocument::class,
+    ];
+
+    private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8;
+
+    /** @var string */
+    private $collectionName;
+
+    /** @var string */
+    private $databaseName;
+
+    /** @var Manager */
+    private $manager;
+
+    /** @var ReadConcern */
+    private $readConcern;
+
+    /** @var ReadPreference */
+    private $readPreference;
+
+    /** @var array */
+    private $typeMap;
+
+    /** @var WriteConcern */
+    private $writeConcern;
+
+    /**
+     * Constructs new Collection instance.
+     *
+     * This class provides methods for collection-specific operations, such as
+     * CRUD (i.e. create, read, update, and delete) and index management.
+     *
+     * Supported options:
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
+     *    use for collection operations. Defaults to the Manager's read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): The default read
+     *    preference to use for collection operations. Defaults to the Manager's
+     *    read preference.
+     *
+     *  * typeMap (array): Default type map for cursors and BSON documents.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
+     *    to use for collection operations. Defaults to the Manager's write
+     *    concern.
+     *
+     * @param Manager $manager        Manager instance from the driver
+     * @param string  $databaseName   Database name
+     * @param string  $collectionName Collection name
+     * @param array   $options        Collection options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(Manager $manager, string $databaseName, string $collectionName, array $options = [])
+    {
+        if (strlen($databaseName) < 1) {
+            throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
+        }
+
+        if (strlen($collectionName) < 1) {
+            throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName);
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
+        }
+
+        $this->manager = $manager;
+        $this->databaseName = $databaseName;
+        $this->collectionName = $collectionName;
+        $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
+        $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
+        $this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP;
+        $this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'collectionName' => $this->collectionName,
+            'databaseName' => $this->databaseName,
+            'manager' => $this->manager,
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+    }
+
+    /**
+     * Return the collection namespace (e.g. "db.collection").
+     *
+     * @see https://mongodb.com/docs/manual/core/databases-and-collections/
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->databaseName . '.' . $this->collectionName;
+    }
+
+    /**
+     * Executes an aggregation framework pipeline on the collection.
+     *
+     * Note: this method's return value depends on the MongoDB server version
+     * and the "useCursor" option. If "useCursor" is true, a Cursor will be
+     * returned; otherwise, an ArrayIterator is returned, which wraps the
+     * "result" array from the command response document.
+     *
+     * @see Aggregate::__construct() for supported options
+     * @param array $pipeline Aggregation pipeline
+     * @param array $options  Command options
+     * @return Traversable
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function aggregate(array $pipeline, array $options = [])
+    {
+        $hasWriteStage = is_last_pipeline_operator_write($pipeline);
+
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $hasWriteStage
+            ? select_server_for_aggregate_write_stage($this->manager, $options)
+            : select_server($this->manager, $options);
+
+        /* MongoDB 4.2 and later supports a read concern when an $out stage is
+         * being used, but earlier versions do not.
+         *
+         * A read concern is also not compatible with transactions.
+         */
+        if (
+            ! isset($options['readConcern']) &&
+            ! is_in_transaction($options) &&
+            ( ! $hasWriteStage || server_supports_feature($server, self::WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE))
+        ) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        if ($hasWriteStage && ! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Executes multiple write operations.
+     *
+     * @see BulkWrite::__construct() for supported options
+     * @param array[] $operations List of write operations
+     * @param array   $options    Command options
+     * @return BulkWriteResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function bulkWrite(array $operations, array $options = [])
+    {
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Gets the number of documents matching the filter.
+     *
+     * @see Count::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Command options
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     *
+     * @deprecated 1.4
+     */
+    public function count($filter = [], array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        $operation = new Count($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Gets the number of documents matching the filter.
+     *
+     * @see CountDocuments::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Command options
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function countDocuments($filter = [], array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        $operation = new CountDocuments($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Create a single index for the collection.
+     *
+     * @see Collection::createIndexes()
+     * @see CreateIndexes::__construct() for supported command options
+     * @param array|object $key     Document containing fields mapped to values,
+     *                              which denote order or an index type
+     * @param array        $options Index and command options
+     * @return string The name of the created index
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function createIndex($key, array $options = [])
+    {
+        $commandOptionKeys = ['commitQuorum' => 1, 'maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1];
+        $indexOptions = array_diff_key($options, $commandOptionKeys);
+        $commandOptions = array_intersect_key($options, $commandOptionKeys);
+
+        return current($this->createIndexes([['key' => $key] + $indexOptions], $commandOptions));
+    }
+
+    /**
+     * Create one or more indexes for the collection.
+     *
+     * Each element in the $indexes array must have a "key" document, which
+     * contains fields mapped to an order or type. Other options may follow.
+     * For example:
+     *
+     *     $indexes = [
+     *         // Create a unique index on the "username" field
+     *         [ 'key' => [ 'username' => 1 ], 'unique' => true ],
+     *         // Create a 2dsphere index on the "loc" field with a custom name
+     *         [ 'key' => [ 'loc' => '2dsphere' ], 'name' => 'geo' ],
+     *     ];
+     *
+     * If the "name" option is unspecified, a name will be generated from the
+     * "key" document.
+     *
+     * @see https://mongodb.com/docs/manual/reference/command/createIndexes/
+     * @see https://mongodb.com/docs/manual/reference/method/db.collection.createIndex/
+     * @see CreateIndexes::__construct() for supported command options
+     * @param array[] $indexes List of index specifications
+     * @param array   $options Command options
+     * @return string[] The names of the created indexes
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function createIndexes(array $indexes, array $options = [])
+    {
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new CreateIndexes($this->databaseName, $this->collectionName, $indexes, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Deletes all documents matching the filter.
+     *
+     * @see DeleteMany::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/delete/
+     * @param array|object $filter  Query by which to delete documents
+     * @param array        $options Command options
+     * @return DeleteResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function deleteMany($filter, array $options = [])
+    {
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Deletes at most one document matching the filter.
+     *
+     * @see DeleteOne::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/delete/
+     * @param array|object $filter  Query by which to delete documents
+     * @param array        $options Command options
+     * @return DeleteResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function deleteOne($filter, array $options = [])
+    {
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds the distinct values for a specified field across the collection.
+     *
+     * @see Distinct::__construct() for supported options
+     * @param string       $fieldName Field for which to return distinct values
+     * @param array|object $filter    Query by which to filter documents
+     * @param array        $options   Command options
+     * @return array
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function distinct(string $fieldName, $filter = [], array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        $operation = new Distinct($this->databaseName, $this->collectionName, $fieldName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop this collection.
+     *
+     * @see DropCollection::__construct() for supported options
+     * @param array $options Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function drop(array $options = [])
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if (! isset($options['encryptedFields'])) {
+            $options['encryptedFields'] = get_encrypted_fields_from_driver($this->databaseName, $this->collectionName, $this->manager)
+                ?? get_encrypted_fields_from_server($this->databaseName, $this->collectionName, $this->manager, $server);
+        }
+
+        $operation = isset($options['encryptedFields'])
+            ? new DropEncryptedCollection($this->databaseName, $this->collectionName, $options)
+            : new DropCollection($this->databaseName, $this->collectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop a single index in the collection.
+     *
+     * @see DropIndexes::__construct() for supported options
+     * @param string|IndexInfo $indexName Index name or model object
+     * @param array            $options   Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function dropIndex($indexName, array $options = [])
+    {
+        $indexName = (string) $indexName;
+
+        if ($indexName === '*') {
+            throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop all indexes in the collection.
+     *
+     * @see DropIndexes::__construct() for supported options
+     * @param array $options Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function dropIndexes(array $options = [])
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropIndexes($this->databaseName, $this->collectionName, '*', $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Gets an estimated number of documents in the collection using the collection metadata.
+     *
+     * @see EstimatedDocumentCount::__construct() for supported options
+     * @param array $options Command options
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function estimatedDocumentCount(array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        $operation = new EstimatedDocumentCount($this->databaseName, $this->collectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Explains explainable commands.
+     *
+     * @see Explain::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/explain/
+     * @param Explainable $explainable Command on which to run explain
+     * @param array       $options     Additional options
+     * @return array|object
+     * @throws UnsupportedException if explainable or options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function explain(Explainable $explainable, array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        $operation = new Explain($this->databaseName, $explainable, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds documents matching the query.
+     *
+     * @see Find::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/crud/#read-operations
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return Cursor
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function find($filter = [], array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new Find($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds a single document matching the query.
+     *
+     * @see FindOne::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/crud/#read-operations
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return array|object|null
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOne($filter = [], array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new FindOne($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds a single document and deletes it, returning the original.
+     *
+     * The document to return may be null if no document matched the filter.
+     *
+     * @see FindOneAndDelete::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/findAndModify/
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Command options
+     * @return array|object|null
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOneAndDelete($filter, array $options = [])
+    {
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds a single document and replaces it, returning either the original or
+     * the replaced document.
+     *
+     * The document to return may be null if no document matched the filter. By
+     * default, the original document is returned. Specify
+     * FindOneAndReplace::RETURN_DOCUMENT_AFTER for the "returnDocument" option
+     * to return the updated document.
+     *
+     * @see FindOneAndReplace::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/findAndModify/
+     * @param array|object $filter      Query by which to filter documents
+     * @param array|object $replacement Replacement document
+     * @param array        $options     Command options
+     * @return array|object|null
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOneAndReplace($filter, $replacement, array $options = [])
+    {
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Finds a single document and updates it, returning either the original or
+     * the updated document.
+     *
+     * The document to return may be null if no document matched the filter. By
+     * default, the original document is returned. Specify
+     * FindOneAndUpdate::RETURN_DOCUMENT_AFTER for the "returnDocument" option
+     * to return the updated document.
+     *
+     * @see FindOneAndReplace::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/findAndModify/
+     * @param array|object $filter  Query by which to filter documents
+     * @param array|object $update  Update to apply to the matched document
+     * @param array        $options Command options
+     * @return array|object|null
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOneAndUpdate($filter, $update, array $options = [])
+    {
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Return the collection name.
+     *
+     * @return string
+     */
+    public function getCollectionName()
+    {
+        return $this->collectionName;
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function getDatabaseName()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Return the Manager.
+     *
+     * @return Manager
+     */
+    public function getManager()
+    {
+        return $this->manager;
+    }
+
+    /**
+     * Return the collection namespace.
+     *
+     * @see https://mongodb.com/docs/manual/reference/glossary/#term-namespace
+     * @return string
+     */
+    public function getNamespace()
+    {
+        return $this->databaseName . '.' . $this->collectionName;
+    }
+
+    /**
+     * Return the read concern for this collection.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
+     * @return ReadConcern
+     */
+    public function getReadConcern()
+    {
+        return $this->readConcern;
+    }
+
+    /**
+     * Return the read preference for this collection.
+     *
+     * @return ReadPreference
+     */
+    public function getReadPreference()
+    {
+        return $this->readPreference;
+    }
+
+    /**
+     * Return the type map for this collection.
+     *
+     * @return array
+     */
+    public function getTypeMap()
+    {
+        return $this->typeMap;
+    }
+
+    /**
+     * Return the write concern for this collection.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
+     * @return WriteConcern
+     */
+    public function getWriteConcern()
+    {
+        return $this->writeConcern;
+    }
+
+    /**
+     * Inserts multiple documents.
+     *
+     * @see InsertMany::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/insert/
+     * @param array[]|object[] $documents The documents to insert
+     * @param array            $options   Command options
+     * @return InsertManyResult
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function insertMany(array $documents, array $options = [])
+    {
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Inserts one document.
+     *
+     * @see InsertOne::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/insert/
+     * @param array|object $document The document to insert
+     * @param array        $options  Command options
+     * @return InsertOneResult
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function insertOne($document, array $options = [])
+    {
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Returns information for all indexes for the collection.
+     *
+     * @see ListIndexes::__construct() for supported options
+     * @return IndexInfoIterator
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function listIndexes(array $options = [])
+    {
+        $operation = new ListIndexes($this->databaseName, $this->collectionName, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Executes a map-reduce aggregation on the collection.
+     *
+     * @see MapReduce::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/mapReduce/
+     * @param JavascriptInterface $map     Map function
+     * @param JavascriptInterface $reduce  Reduce function
+     * @param string|array|object $out     Output specification
+     * @param array               $options Command options
+     * @return MapReduceResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     * @throws UnexpectedValueException if the command response was malformed
+     */
+    public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = [])
+    {
+        $hasOutputCollection = ! is_mapreduce_output_inline($out);
+
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        // Check if the out option is inline because we will want to coerce a primary read preference if not
+        if ($hasOutputCollection) {
+            $options['readPreference'] = new ReadPreference(ReadPreference::PRIMARY);
+        }
+
+        $server = select_server($this->manager, $options);
+
+        /* A "majority" read concern is not compatible with inline output, so
+         * avoid providing the Collection's read concern if it would conflict.
+         *
+         * A read concern is also not compatible with transactions.
+         */
+        if (! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new MapReduce($this->databaseName, $this->collectionName, $map, $reduce, $out, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Renames the collection.
+     *
+     * @see RenameCollection::__construct() for supported options
+     * @param string      $toCollectionName New name of the collection
+     * @param string|null $toDatabaseName   New database name of the collection. Defaults to the original database.
+     * @param array       $options          Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function rename(string $toCollectionName, ?string $toDatabaseName = null, array $options = [])
+    {
+        if (! isset($toDatabaseName)) {
+            $toDatabaseName = $this->databaseName;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new RenameCollection($this->databaseName, $this->collectionName, $toDatabaseName, $toCollectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Replaces at most one document matching the filter.
+     *
+     * @see ReplaceOne::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/update/
+     * @param array|object $filter      Query by which to filter documents
+     * @param array|object $replacement Replacement document
+     * @param array        $options     Command options
+     * @return UpdateResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function replaceOne($filter, $replacement, array $options = [])
+    {
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Updates all documents matching the filter.
+     *
+     * @see UpdateMany::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/update/
+     * @param array|object $filter  Query by which to filter documents
+     * @param array|object $update  Update to apply to the matched documents
+     * @param array        $options Command options
+     * @return UpdateResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function updateMany($filter, $update, array $options = [])
+    {
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Updates at most one document matching the filter.
+     *
+     * @see UpdateOne::__construct() for supported options
+     * @see https://mongodb.com/docs/manual/reference/command/update/
+     * @param array|object $filter  Query by which to filter documents
+     * @param array|object $update  Update to apply to the matched document
+     * @param array        $options Command options
+     * @return UpdateResult
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function updateOne($filter, $update, array $options = [])
+    {
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Create a change stream for watching changes to the collection.
+     *
+     * @see Watch::__construct() for supported options
+     * @param array $pipeline Aggregation pipeline
+     * @param array $options  Command options
+     * @return ChangeStream
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function watch(array $pipeline = [], array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        /* Although change streams require a newer version of the server than
+         * read concerns, perform the usual wire version check before inheriting
+         * the collection's read concern. In the event that the server is too
+         * old, this makes it more likely that users will encounter an error
+         * related to change streams being unsupported instead of an
+         * UnsupportedException regarding use of the "readConcern" option from
+         * the Aggregate operation class. */
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new Watch($this->manager, $this->databaseName, $this->collectionName, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Get a clone of this collection with different options.
+     *
+     * @see Collection::__construct() for supported options
+     * @param array $options Collection constructor options
+     * @return Collection
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function withOptions(array $options = [])
+    {
+        $options += [
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
+    }
+}

+ 154 - 0
api/v1/vendor/mongodb/mongodb/src/Command/ListCollections.php

@@ -0,0 +1,154 @@
+<?php
+/*
+ * Copyright 2020-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Command;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Model\CachingIterator;
+use MongoDB\Operation\Executable;
+
+use function is_array;
+use function is_bool;
+use function is_integer;
+use function is_object;
+
+/**
+ * Wrapper for the listCollections command.
+ *
+ * @internal
+ * @see https://mongodb.com/docs/manual/reference/command/listCollections/
+ */
+class ListCollections implements Executable
+{
+    /** @var string */
+    private $databaseName;
+
+    /** @var array */
+    private $options;
+
+    /**
+     * Constructs a listCollections command.
+     *
+     * Supported options:
+     *
+     *  * authorizedCollections (boolean): Determines which collections are
+     *    returned based on the user privileges.
+     *
+     *    For servers < 4.0, this option is ignored.
+     *
+     *  * comment (mixed): BSON value to attach as a comment to this command.
+     *
+     *    This is not supported for servers versions < 4.4.
+     *
+     *  * filter (document): Query by which to filter collections.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * nameOnly (boolean): A flag to indicate whether the command should
+     *    return just the collection/view names and type or return both the name
+     *    and other information.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     * @param string $databaseName Database name
+     * @param array  $options      Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, array $options = [])
+    {
+        if (isset($options['authorizedCollections']) && ! is_bool($options['authorizedCollections'])) {
+            throw InvalidArgumentException::invalidType('"authorizedCollections" option', $options['authorizedCollections'], 'boolean');
+        }
+
+        if (isset($options['filter']) && ! is_array($options['filter']) && ! is_object($options['filter'])) {
+            throw InvalidArgumentException::invalidType('"filter" option', $options['filter'], 'array or object');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['nameOnly']) && ! is_bool($options['nameOnly'])) {
+            throw InvalidArgumentException::invalidType('"nameOnly" option', $options['nameOnly'], 'boolean');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
+        }
+
+        $this->databaseName = $databaseName;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server): CachingIterator
+    {
+        $cursor = $server->executeReadCommand($this->databaseName, $this->createCommand(), $this->createOptions());
+        $cursor->setTypeMap(['root' => 'array', 'document' => 'array']);
+
+        return new CachingIterator($cursor);
+    }
+
+    /**
+     * Create the listCollections command.
+     */
+    private function createCommand(): Command
+    {
+        $cmd = ['listCollections' => 1];
+
+        if (! empty($this->options['filter'])) {
+            $cmd['filter'] = (object) $this->options['filter'];
+        }
+
+        foreach (['authorizedCollections', 'comment', 'maxTimeMS', 'nameOnly'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        return new Command($cmd);
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * Note: read preference is intentionally omitted, as the spec requires that
+     * the command be executed on the primary.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-server.executecommand.php
+     */
+    private function createOptions(): array
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        return $options;
+    }
+}

+ 157 - 0
api/v1/vendor/mongodb/mongodb/src/Command/ListDatabases.php

@@ -0,0 +1,157 @@
+<?php
+/*
+ * Copyright 2020-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Command;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Operation\Executable;
+
+use function current;
+use function is_array;
+use function is_bool;
+use function is_integer;
+use function is_object;
+
+/**
+ * Wrapper for the ListDatabases command.
+ *
+ * @internal
+ * @see https://mongodb.com/docs/manual/reference/command/listDatabases/
+ */
+class ListDatabases implements Executable
+{
+    /** @var array */
+    private $options;
+
+    /**
+     * Constructs a listDatabases command.
+     *
+     * Supported options:
+     *
+     *  * authorizedDatabases (boolean): Determines which databases are returned
+     *    based on the user privileges.
+     *
+     *    For servers < 4.0.5, this option is ignored.
+     *
+     *  * comment (mixed): BSON value to attach as a comment to this command.
+     *
+     *    This is not supported for servers versions < 4.4.
+     *
+     *  * filter (document): Query by which to filter databases.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * nameOnly (boolean): A flag to indicate whether the command should
+     *    return just the database names, or return both database names and size
+     *    information.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     * @param array $options Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(array $options = [])
+    {
+        if (isset($options['authorizedDatabases']) && ! is_bool($options['authorizedDatabases'])) {
+            throw InvalidArgumentException::invalidType('"authorizedDatabases" option', $options['authorizedDatabases'], 'boolean');
+        }
+
+        if (isset($options['filter']) && ! is_array($options['filter']) && ! is_object($options['filter'])) {
+            throw InvalidArgumentException::invalidType('"filter" option', $options['filter'], ['array', 'object']);
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['nameOnly']) && ! is_bool($options['nameOnly'])) {
+            throw InvalidArgumentException::invalidType('"nameOnly" option', $options['nameOnly'], 'boolean');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
+        }
+
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @return array An array of database info structures
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server): array
+    {
+        $cursor = $server->executeReadCommand('admin', $this->createCommand(), $this->createOptions());
+        $cursor->setTypeMap(['root' => 'array', 'document' => 'array']);
+        $result = current($cursor->toArray());
+
+        if (! isset($result['databases']) || ! is_array($result['databases'])) {
+            throw new UnexpectedValueException('listDatabases command did not return a "databases" array');
+        }
+
+        return $result['databases'];
+    }
+
+    /**
+     * Create the listDatabases command.
+     */
+    private function createCommand(): Command
+    {
+        $cmd = ['listDatabases' => 1];
+
+        if (! empty($this->options['filter'])) {
+            $cmd['filter'] = (object) $this->options['filter'];
+        }
+
+        foreach (['authorizedDatabases', 'comment', 'maxTimeMS', 'nameOnly'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        return new Command($cmd);
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * Note: read preference is intentionally omitted, as the spec requires that
+     * the command be executed on the primary.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-server.executecommand.php
+     */
+    private function createOptions(): array
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        return $options;
+    }
+}

+ 646 - 0
api/v1/vendor/mongodb/mongodb/src/Database.php

@@ -0,0 +1,646 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use Iterator;
+use MongoDB\Driver\ClientEncryption;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Exception\CreateEncryptedCollectionException;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\GridFS\Bucket;
+use MongoDB\Model\BSONArray;
+use MongoDB\Model\BSONDocument;
+use MongoDB\Model\CollectionInfoIterator;
+use MongoDB\Operation\Aggregate;
+use MongoDB\Operation\CreateCollection;
+use MongoDB\Operation\CreateEncryptedCollection;
+use MongoDB\Operation\DatabaseCommand;
+use MongoDB\Operation\DropCollection;
+use MongoDB\Operation\DropDatabase;
+use MongoDB\Operation\DropEncryptedCollection;
+use MongoDB\Operation\ListCollectionNames;
+use MongoDB\Operation\ListCollections;
+use MongoDB\Operation\ModifyCollection;
+use MongoDB\Operation\RenameCollection;
+use MongoDB\Operation\Watch;
+use Throwable;
+use Traversable;
+
+use function is_array;
+use function strlen;
+
+class Database
+{
+    private const DEFAULT_TYPE_MAP = [
+        'array' => BSONArray::class,
+        'document' => BSONDocument::class,
+        'root' => BSONDocument::class,
+    ];
+
+    private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8;
+
+    /** @var string */
+    private $databaseName;
+
+    /** @var Manager */
+    private $manager;
+
+    /** @var ReadConcern */
+    private $readConcern;
+
+    /** @var ReadPreference */
+    private $readPreference;
+
+    /** @var array */
+    private $typeMap;
+
+    /** @var WriteConcern */
+    private $writeConcern;
+
+    /**
+     * Constructs new Database instance.
+     *
+     * This class provides methods for database-specific operations and serves
+     * as a gateway for accessing collections.
+     *
+     * Supported options:
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
+     *    use for database operations and selected collections. Defaults to the
+     *    Manager's read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): The default read
+     *    preference to use for database operations and selected collections.
+     *    Defaults to the Manager's read preference.
+     *
+     *  * typeMap (array): Default type map for cursors and BSON documents.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
+     *    to use for database operations and selected collections. Defaults to
+     *    the Manager's write concern.
+     *
+     * @param Manager $manager      Manager instance from the driver
+     * @param string  $databaseName Database name
+     * @param array   $options      Database options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(Manager $manager, string $databaseName, array $options = [])
+    {
+        if (strlen($databaseName) < 1) {
+            throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
+        }
+
+        $this->manager = $manager;
+        $this->databaseName = $databaseName;
+        $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
+        $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
+        $this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP;
+        $this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'databaseName' => $this->databaseName,
+            'manager' => $this->manager,
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+    }
+
+    /**
+     * Select a collection within this database.
+     *
+     * Note: collections whose names contain special characters (e.g. ".") may
+     * be selected with complex syntax (e.g. $database->{"system.profile"}) or
+     * {@link selectCollection()}.
+     *
+     * @see https://php.net/oop5.overloading#object.get
+     * @see https://php.net/types.string#language.types.string.parsing.complex
+     * @param string $collectionName Name of the collection to select
+     * @return Collection
+     */
+    public function __get(string $collectionName)
+    {
+        return $this->selectCollection($collectionName);
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Runs an aggregation framework pipeline on the database for pipeline
+     * stages that do not require an underlying collection, such as $currentOp
+     * and $listLocalSessions. Requires MongoDB >= 3.6
+     *
+     * @see Aggregate::__construct() for supported options
+     * @param array $pipeline Aggregation pipeline
+     * @param array $options  Command options
+     * @return Traversable
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function aggregate(array $pipeline, array $options = [])
+    {
+        $hasWriteStage = is_last_pipeline_operator_write($pipeline);
+
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = $hasWriteStage
+            ? select_server_for_aggregate_write_stage($this->manager, $options)
+            : select_server($this->manager, $options);
+
+        /* MongoDB 4.2 and later supports a read concern when an $out stage is
+         * being used, but earlier versions do not.
+         *
+         * A read concern is also not compatible with transactions.
+         */
+        if (
+            ! isset($options['readConcern']) &&
+            ! is_in_transaction($options) &&
+            ( ! $hasWriteStage || server_supports_feature($server, self::WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE))
+        ) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        if ($hasWriteStage && ! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new Aggregate($this->databaseName, null, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Execute a command on this database.
+     *
+     * @see DatabaseCommand::__construct() for supported options
+     * @param array|object $command Command document
+     * @param array        $options Options for command execution
+     * @return Cursor
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function command($command, array $options = [])
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new DatabaseCommand($this->databaseName, $command, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Create a new collection explicitly.
+     *
+     * If the "encryptedFields" option is specified, this method additionally
+     * creates related metadata collections and an index on the encrypted
+     * collection.
+     *
+     * @see CreateCollection::__construct() for supported options
+     * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#create-collection-helper
+     * @see https://www.mongodb.com/docs/manual/core/queryable-encryption/fundamentals/manage-collections/
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function createCollection(string $collectionName, array $options = [])
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if (! isset($options['encryptedFields'])) {
+            $options['encryptedFields'] = get_encrypted_fields_from_driver($this->databaseName, $collectionName, $this->manager);
+        }
+
+        $operation = isset($options['encryptedFields'])
+            ? new CreateEncryptedCollection($this->databaseName, $collectionName, $options)
+            : new CreateCollection($this->databaseName, $collectionName, $options);
+
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Create a new encrypted collection explicitly.
+     *
+     * The "encryptedFields" option is required.
+     *
+     * This method will automatically create data keys for any encrypted fields
+     * where "keyId" is null. A copy of the modified "encryptedFields" option
+     * will be returned in addition to the result from creating the collection.
+     *
+     * If any error is encountered creating data keys or the collection, a
+     * CreateEncryptedCollectionException will be thrown. The original exception
+     * and modified "encryptedFields" option can be accessed via the
+     * getPrevious() and getEncryptedFields() methods, respectively.
+     *
+     * @see CreateCollection::__construct() for supported options
+     * @return array A tuple containing the command result document from creating the collection and the modified "encryptedFields" option
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws CreateEncryptedCollectionException for any errors creating data keys or creating the collection
+     * @throws UnsupportedException if Queryable Encryption is not supported by the selected server
+     */
+    public function createEncryptedCollection(string $collectionName, ClientEncryption $clientEncryption, string $kmsProvider, ?array $masterKey, array $options): array
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new CreateEncryptedCollection($this->databaseName, $collectionName, $options);
+        $server = select_server($this->manager, $options);
+
+        try {
+            $operation->createDataKeys($clientEncryption, $kmsProvider, $masterKey, $encryptedFields);
+            $result = $operation->execute($server);
+
+            return [$result, $encryptedFields];
+        } catch (Throwable $e) {
+            throw new CreateEncryptedCollectionException($e, $encryptedFields ?? []);
+        }
+    }
+
+    /**
+     * Drop this database.
+     *
+     * @see DropDatabase::__construct() for supported options
+     * @param array $options Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are unsupported on the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function drop(array $options = [])
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new DropDatabase($this->databaseName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Drop a collection within this database.
+     *
+     * @see DropCollection::__construct() for supported options
+     * @param string $collectionName Collection name
+     * @param array  $options        Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are unsupported on the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function dropCollection(string $collectionName, array $options = [])
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        if (! isset($options['encryptedFields'])) {
+            $options['encryptedFields'] = get_encrypted_fields_from_driver($this->databaseName, $collectionName, $this->manager)
+                ?? get_encrypted_fields_from_server($this->databaseName, $collectionName, $this->manager, $server);
+        }
+
+        $operation = isset($options['encryptedFields'])
+            ? new DropEncryptedCollection($this->databaseName, $collectionName, $options)
+            : new DropCollection($this->databaseName, $collectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Returns the database name.
+     *
+     * @return string
+     */
+    public function getDatabaseName()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Return the Manager.
+     *
+     * @return Manager
+     */
+    public function getManager()
+    {
+        return $this->manager;
+    }
+
+    /**
+     * Return the read concern for this database.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
+     * @return ReadConcern
+     */
+    public function getReadConcern()
+    {
+        return $this->readConcern;
+    }
+
+    /**
+     * Return the read preference for this database.
+     *
+     * @return ReadPreference
+     */
+    public function getReadPreference()
+    {
+        return $this->readPreference;
+    }
+
+    /**
+     * Return the type map for this database.
+     *
+     * @return array
+     */
+    public function getTypeMap()
+    {
+        return $this->typeMap;
+    }
+
+    /**
+     * Return the write concern for this database.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
+     * @return WriteConcern
+     */
+    public function getWriteConcern()
+    {
+        return $this->writeConcern;
+    }
+
+    /**
+     * Returns the names of all collections in this database
+     *
+     * @see ListCollectionNames::__construct() for supported options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function listCollectionNames(array $options = []): Iterator
+    {
+        $operation = new ListCollectionNames($this->databaseName, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Returns information for all collections in this database.
+     *
+     * @see ListCollections::__construct() for supported options
+     * @return CollectionInfoIterator
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function listCollections(array $options = [])
+    {
+        $operation = new ListCollections($this->databaseName, $options);
+        $server = select_server($this->manager, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Modifies a collection or view.
+     *
+     * @see ModifyCollection::__construct() for supported options
+     * @param string $collectionName    Collection or view to modify
+     * @param array  $collectionOptions Collection or view options to assign
+     * @param array  $options           Command options
+     * @return array|object
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function modifyCollection(string $collectionName, array $collectionOptions, array $options = [])
+    {
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new ModifyCollection($this->databaseName, $collectionName, $collectionOptions, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Rename a collection within this database.
+     *
+     * @see RenameCollection::__construct() for supported options
+     * @param string      $fromCollectionName Collection name
+     * @param string      $toCollectionName   New name of the collection
+     * @param string|null $toDatabaseName     New database name of the collection. Defaults to the original database.
+     * @param array       $options            Additional options
+     * @return array|object Command result document
+     * @throws UnsupportedException if options are unsupported on the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function renameCollection(string $fromCollectionName, string $toCollectionName, ?string $toDatabaseName = null, array $options = [])
+    {
+        if (! isset($toDatabaseName)) {
+            $toDatabaseName = $this->databaseName;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['writeConcern']) && ! is_in_transaction($options)) {
+            $options['writeConcern'] = $this->writeConcern;
+        }
+
+        $operation = new RenameCollection($this->databaseName, $fromCollectionName, $toDatabaseName, $toCollectionName, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Select a collection within this database.
+     *
+     * @see Collection::__construct() for supported options
+     * @param string $collectionName Name of the collection to select
+     * @param array  $options        Collection constructor options
+     * @return Collection
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function selectCollection(string $collectionName, array $options = [])
+    {
+        $options += [
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        return new Collection($this->manager, $this->databaseName, $collectionName, $options);
+    }
+
+    /**
+     * Select a GridFS bucket within this database.
+     *
+     * @see Bucket::__construct() for supported options
+     * @param array $options Bucket constructor options
+     * @return Bucket
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function selectGridFSBucket(array $options = [])
+    {
+        $options += [
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        return new Bucket($this->manager, $this->databaseName, $options);
+    }
+
+    /**
+     * Create a change stream for watching changes to the database.
+     *
+     * @see Watch::__construct() for supported options
+     * @param array $pipeline Aggregation pipeline
+     * @param array $options  Command options
+     * @return ChangeStream
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function watch(array $pipeline = [], array $options = [])
+    {
+        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
+            $options['readPreference'] = $this->readPreference;
+        }
+
+        $server = select_server($this->manager, $options);
+
+        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
+            $options['readConcern'] = $this->readConcern;
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = $this->typeMap;
+        }
+
+        $operation = new Watch($this->manager, $this->databaseName, null, $pipeline, $options);
+
+        return $operation->execute($server);
+    }
+
+    /**
+     * Get a clone of this database with different options.
+     *
+     * @see Database::__construct() for supported options
+     * @param array $options Database constructor options
+     * @return Database
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function withOptions(array $options = [])
+    {
+        $options += [
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+
+        return new Database($this->manager, $this->databaseName, $options);
+    }
+}

+ 70 - 0
api/v1/vendor/mongodb/mongodb/src/DeleteResult.php

@@ -0,0 +1,70 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\WriteResult;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Result class for a delete operation.
+ */
+class DeleteResult
+{
+    /** @var WriteResult */
+    private $writeResult;
+
+    /** @var boolean */
+    private $isAcknowledged;
+
+    public function __construct(WriteResult $writeResult)
+    {
+        $this->writeResult = $writeResult;
+        $this->isAcknowledged = $writeResult->isAcknowledged();
+    }
+
+    /**
+     * Return the number of documents that were deleted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see DeleteResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getDeletedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getDeletedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return whether this delete was acknowledged by the server.
+     *
+     * If the delete was not acknowledged, other fields from the WriteResult
+     * (e.g. deletedCount) will be undefined.
+     *
+     * @return boolean
+     */
+    public function isAcknowledged()
+    {
+        return $this->isAcknowledged;
+    }
+}

+ 47 - 0
api/v1/vendor/mongodb/mongodb/src/Exception/BadMethodCallException.php

@@ -0,0 +1,47 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+use BadMethodCallException as BaseBadMethodCallException;
+
+use function sprintf;
+
+class BadMethodCallException extends BaseBadMethodCallException implements Exception
+{
+    /**
+     * Thrown when a mutable method is invoked on an immutable object.
+     *
+     * @param string $class Class name
+     * @return self
+     */
+    public static function classIsImmutable(string $class)
+    {
+        return new static(sprintf('%s is immutable', $class));
+    }
+
+    /**
+     * Thrown when accessing a result field on an unacknowledged write result.
+     *
+     * @param string $method Method name
+     * @return self
+     */
+    public static function unacknowledgedWriteResultAccess(string $method)
+    {
+        return new static(sprintf('%s should not be called for an unacknowledged write result', $method));
+    }
+}

+ 56 - 0
api/v1/vendor/mongodb/mongodb/src/Exception/CreateEncryptedCollectionException.php

@@ -0,0 +1,56 @@
+<?php
+/*
+ * Copyright 2023-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+use Throwable;
+
+use function get_class;
+use function sprintf;
+
+/**
+ * @internal
+ * @see \MongoDB\Database::createEncryptedCollection()
+ */
+final class CreateEncryptedCollectionException extends RuntimeException
+{
+    /** @var array */
+    private $encryptedFields;
+
+    public function __construct(Throwable $previous, array $encryptedFields)
+    {
+        parent::__construct(sprintf('Creating encrypted collection failed due to previous %s: %s', get_class($previous), $previous->getMessage()), 0, $previous);
+
+        $this->encryptedFields = $encryptedFields;
+    }
+
+    /**
+     * Returns the encryptedFields option in its last known state before the
+     * operation was interrupted.
+     *
+     * This can be used to infer which data keys were successfully created;
+     * however, it is possible that additional data keys were successfully
+     * created and are not present in the returned value. For example, if the
+     * operation was interrupted by a timeout error when creating a data key,
+     * the write may actually have succeeded on the server but the key will not
+     * be present in the returned value.
+     */
+    public function getEncryptedFields(): array
+    {
+        return $this->encryptedFields;
+    }
+}

+ 24 - 0
api/v1/vendor/mongodb/mongodb/src/Exception/Exception.php

@@ -0,0 +1,24 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+use MongoDB\Driver\Exception\Exception as DriverException;
+
+interface Exception extends DriverException
+{
+}

+ 62 - 0
api/v1/vendor/mongodb/mongodb/src/Exception/InvalidArgumentException.php

@@ -0,0 +1,62 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException;
+
+use function array_pop;
+use function assert;
+use function count;
+use function get_debug_type;
+use function implode;
+use function is_array;
+use function sprintf;
+
+class InvalidArgumentException extends DriverInvalidArgumentException implements Exception
+{
+    /**
+     * Thrown when an argument or option has an invalid type.
+     *
+     * @param string              $name         Name of the argument or option
+     * @param mixed               $value        Actual value (used to derive the type)
+     * @param string|list<string> $expectedType Expected type as a string or an array containing one or more strings
+     * @return self
+     */
+    public static function invalidType(string $name, $value, $expectedType)
+    {
+        if (is_array($expectedType)) {
+            $expectedType = self::expectedTypesToString($expectedType);
+        }
+
+        return new static(sprintf('Expected %s to have type "%s" but found "%s"', $name, $expectedType, get_debug_type($value)));
+    }
+
+    /** @param list<string> $types */
+    private static function expectedTypesToString(array $types): string
+    {
+        assert(count($types) > 0);
+
+        if (count($types) < 3) {
+            return implode('" or "', $types);
+        }
+
+        $lastType = array_pop($types);
+
+        return sprintf('%s", or "%s', implode('", "', $types), $lastType);
+    }
+}

+ 45 - 0
api/v1/vendor/mongodb/mongodb/src/Exception/ResumeTokenException.php

@@ -0,0 +1,45 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+use function get_debug_type;
+use function sprintf;
+
+class ResumeTokenException extends RuntimeException
+{
+    /**
+     * Thrown when a resume token has an invalid type.
+     *
+     * @param mixed $value Actual value (used to derive the type)
+     * @return self
+     */
+    public static function invalidType($value)
+    {
+        return new static(sprintf('Expected resume token to have type "array or object" but found "%s"', get_debug_type($value)));
+    }
+
+    /**
+     * Thrown when a resume token is not found in a change document.
+     *
+     * @return self
+     */
+    public static function notFound()
+    {
+        return new static('Resume token not found in change document');
+    }
+}

+ 24 - 0
api/v1/vendor/mongodb/mongodb/src/Exception/RuntimeException.php

@@ -0,0 +1,24 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+
+class RuntimeException extends DriverRuntimeException implements Exception
+{
+}

+ 24 - 0
api/v1/vendor/mongodb/mongodb/src/Exception/UnexpectedValueException.php

@@ -0,0 +1,24 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+use MongoDB\Driver\Exception\UnexpectedValueException as DriverUnexpectedValueException;
+
+class UnexpectedValueException extends DriverUnexpectedValueException implements Exception
+{
+}

+ 128 - 0
api/v1/vendor/mongodb/mongodb/src/Exception/UnsupportedException.php

@@ -0,0 +1,128 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Exception;
+
+class UnsupportedException extends RuntimeException
+{
+    /**
+     * Thrown when a command's allowDiskUse option is not supported by a server.
+     *
+     * @return self
+     */
+    public static function allowDiskUseNotSupported()
+    {
+        return new static('The "allowDiskUse" option is not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when array filters are not supported by a server.
+     *
+     * @deprecated 1.12
+     * @todo Remove this in 2.0 (see: PHPLIB-797)
+     *
+     * @return self
+     */
+    public static function arrayFiltersNotSupported()
+    {
+        return new static('Array filters are not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when collations are not supported by a server.
+     *
+     * @deprecated 1.12
+     * @todo Remove this in 2.0 (see: PHPLIB-797)
+     *
+     * @return self
+     */
+    public static function collationNotSupported()
+    {
+        return new static('Collations are not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when the commitQuorum option for createIndexes is not supported
+     * by a server.
+     *
+     * @return self
+     */
+    public static function commitQuorumNotSupported()
+    {
+        return new static('The "commitQuorum" option is not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when explain is not supported by a server.
+     *
+     * @return self
+     */
+    public static function explainNotSupported()
+    {
+        return new static('Explain is not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when a command's hint option is not supported by a server.
+     *
+     * @return self
+     */
+    public static function hintNotSupported()
+    {
+        return new static('Hint is not supported by the server executing this operation');
+    }
+
+    /**
+     * Thrown when a command's readConcern option is not supported by a server.
+     *
+     * @return self
+     */
+    public static function readConcernNotSupported()
+    {
+        return new static('Read concern is not supported by the server executing this command');
+    }
+
+    /**
+     * Thrown when a readConcern is used with a read operation in a transaction.
+     *
+     * @return self
+     */
+    public static function readConcernNotSupportedInTransaction()
+    {
+        return new static('The "readConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.');
+    }
+
+    /**
+     * Thrown when a command's writeConcern option is not supported by a server.
+     *
+     * @return self
+     */
+    public static function writeConcernNotSupported()
+    {
+        return new static('Write concern is not supported by the server executing this command');
+    }
+
+    /**
+     * Thrown when a writeConcern is used with a write operation in a transaction.
+     *
+     * @return self
+     */
+    public static function writeConcernNotSupportedInTransaction()
+    {
+        return new static('The "writeConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.');
+    }
+}

+ 736 - 0
api/v1/vendor/mongodb/mongodb/src/GridFS/Bucket.php

@@ -0,0 +1,736 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use MongoDB\Collection;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\GridFS\Exception\CorruptFileException;
+use MongoDB\GridFS\Exception\FileNotFoundException;
+use MongoDB\GridFS\Exception\StreamException;
+use MongoDB\Model\BSONArray;
+use MongoDB\Model\BSONDocument;
+use MongoDB\Operation\Find;
+
+use function array_intersect_key;
+use function assert;
+use function fopen;
+use function get_resource_type;
+use function in_array;
+use function is_array;
+use function is_bool;
+use function is_integer;
+use function is_object;
+use function is_resource;
+use function is_string;
+use function method_exists;
+use function MongoDB\apply_type_map_to_document;
+use function MongoDB\BSON\fromPHP;
+use function MongoDB\BSON\toJSON;
+use function property_exists;
+use function sprintf;
+use function stream_context_create;
+use function stream_copy_to_stream;
+use function stream_get_meta_data;
+use function stream_get_wrappers;
+use function urlencode;
+
+/**
+ * Bucket provides a public API for interacting with the GridFS files and chunks
+ * collections.
+ */
+class Bucket
+{
+    private const DEFAULT_BUCKET_NAME = 'fs';
+
+    private const DEFAULT_CHUNK_SIZE_BYTES = 261120;
+
+    private const DEFAULT_TYPE_MAP = [
+        'array' => BSONArray::class,
+        'document' => BSONDocument::class,
+        'root' => BSONDocument::class,
+    ];
+
+    private const STREAM_WRAPPER_PROTOCOL = 'gridfs';
+
+    /** @var CollectionWrapper */
+    private $collectionWrapper;
+
+    /** @var string */
+    private $databaseName;
+
+    /** @var Manager */
+    private $manager;
+
+    /** @var string */
+    private $bucketName;
+
+    /** @var boolean */
+    private $disableMD5;
+
+    /** @var integer */
+    private $chunkSizeBytes;
+
+    /** @var ReadConcern */
+    private $readConcern;
+
+    /** @var ReadPreference */
+    private $readPreference;
+
+    /** @var array */
+    private $typeMap;
+
+    /** @var WriteConcern */
+    private $writeConcern;
+
+    /**
+     * Constructs a GridFS bucket.
+     *
+     * Supported options:
+     *
+     *  * bucketName (string): The bucket name, which will be used as a prefix
+     *    for the files and chunks collections. Defaults to "fs".
+     *
+     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
+     *    261120 (i.e. 255 KiB).
+     *
+     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
+     *    each stored file. Defaults to "false".
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *  * typeMap (array): Default type map for cursors and BSON documents.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @param Manager $manager      Manager instance from the driver
+     * @param string  $databaseName Database name
+     * @param array   $options      Bucket options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(Manager $manager, string $databaseName, array $options = [])
+    {
+        $options += [
+            'bucketName' => self::DEFAULT_BUCKET_NAME,
+            'chunkSizeBytes' => self::DEFAULT_CHUNK_SIZE_BYTES,
+            'disableMD5' => false,
+        ];
+
+        if (! is_string($options['bucketName'])) {
+            throw InvalidArgumentException::invalidType('"bucketName" option', $options['bucketName'], 'string');
+        }
+
+        if (! is_integer($options['chunkSizeBytes'])) {
+            throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
+        }
+
+        if ($options['chunkSizeBytes'] < 1) {
+            throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
+        }
+
+        if (! is_bool($options['disableMD5'])) {
+            throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
+        }
+
+        $this->manager = $manager;
+        $this->databaseName = $databaseName;
+        $this->bucketName = $options['bucketName'];
+        $this->chunkSizeBytes = $options['chunkSizeBytes'];
+        $this->disableMD5 = $options['disableMD5'];
+        $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
+        $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
+        $this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP;
+        $this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
+
+        $collectionOptions = array_intersect_key($options, ['readConcern' => 1, 'readPreference' => 1, 'typeMap' => 1, 'writeConcern' => 1]);
+
+        $this->collectionWrapper = new CollectionWrapper($manager, $databaseName, $options['bucketName'], $collectionOptions);
+        $this->registerStreamWrapper();
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'bucketName' => $this->bucketName,
+            'databaseName' => $this->databaseName,
+            'disableMD5' => $this->disableMD5,
+            'manager' => $this->manager,
+            'chunkSizeBytes' => $this->chunkSizeBytes,
+            'readConcern' => $this->readConcern,
+            'readPreference' => $this->readPreference,
+            'typeMap' => $this->typeMap,
+            'writeConcern' => $this->writeConcern,
+        ];
+    }
+
+    /**
+     * Delete a file from the GridFS bucket.
+     *
+     * If the files collection document is not found, this method will still
+     * attempt to delete orphaned chunks.
+     *
+     * @param mixed $id File ID
+     * @throws FileNotFoundException if no file could be selected
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function delete($id)
+    {
+        $file = $this->collectionWrapper->findFileById($id);
+        $this->collectionWrapper->deleteFileAndChunksById($id);
+
+        if ($file === null) {
+            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+        }
+    }
+
+    /**
+     * Writes the contents of a GridFS file to a writable stream.
+     *
+     * @param mixed    $id          File ID
+     * @param resource $destination Writable Stream
+     * @throws FileNotFoundException if no file could be selected
+     * @throws InvalidArgumentException if $destination is not a stream
+     * @throws StreamException if the file could not be uploaded
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function downloadToStream($id, $destination)
+    {
+        if (! is_resource($destination) || get_resource_type($destination) != "stream") {
+            throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
+        }
+
+        $source = $this->openDownloadStream($id);
+        if (@stream_copy_to_stream($source, $destination) === false) {
+            throw StreamException::downloadFromIdFailed($id, $source, $destination);
+        }
+    }
+
+    /**
+     * Writes the contents of a GridFS file, which is selected by name and
+     * revision, to a writable stream.
+     *
+     * Supported options:
+     *
+     *  * revision (integer): Which revision (i.e. documents with the same
+     *    filename and different uploadDate) of the file to retrieve. Defaults
+     *    to -1 (i.e. the most recent revision).
+     *
+     * Revision numbers are defined as follows:
+     *
+     *  * 0 = the original stored file
+     *  * 1 = the first revision
+     *  * 2 = the second revision
+     *  * etc…
+     *  * -2 = the second most recent revision
+     *  * -1 = the most recent revision
+     *
+     * @param string   $filename    Filename
+     * @param resource $destination Writable Stream
+     * @param array    $options     Download options
+     * @throws FileNotFoundException if no file could be selected
+     * @throws InvalidArgumentException if $destination is not a stream
+     * @throws StreamException if the file could not be uploaded
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function downloadToStreamByName(string $filename, $destination, array $options = [])
+    {
+        if (! is_resource($destination) || get_resource_type($destination) != "stream") {
+            throw InvalidArgumentException::invalidType('$destination', $destination, 'resource');
+        }
+
+        $source = $this->openDownloadStreamByName($filename, $options);
+        if (@stream_copy_to_stream($source, $destination) === false) {
+            throw StreamException::downloadFromFilenameFailed($filename, $source, $destination);
+        }
+    }
+
+    /**
+     * Drops the files and chunks collections associated with this GridFS
+     * bucket.
+     *
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function drop()
+    {
+        $this->collectionWrapper->dropCollections();
+    }
+
+    /**
+     * Finds documents from the GridFS bucket's files collection matching the
+     * query.
+     *
+     * @see Find::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return Cursor
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function find($filter = [], array $options = [])
+    {
+        return $this->collectionWrapper->findFiles($filter, $options);
+    }
+
+    /**
+     * Finds a single document from the GridFS bucket's files collection
+     * matching the query.
+     *
+     * @see FindOne::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return array|object|null
+     * @throws UnsupportedException if options are not supported by the selected server
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function findOne($filter = [], array $options = [])
+    {
+        return $this->collectionWrapper->findOneFile($filter, $options);
+    }
+
+    /**
+     * Return the bucket name.
+     *
+     * @return string
+     */
+    public function getBucketName()
+    {
+        return $this->bucketName;
+    }
+
+    /**
+     * Return the chunks collection.
+     *
+     * @return Collection
+     */
+    public function getChunksCollection()
+    {
+        return $this->collectionWrapper->getChunksCollection();
+    }
+
+    /**
+     * Return the chunk size in bytes.
+     *
+     * @return integer
+     */
+    public function getChunkSizeBytes()
+    {
+        return $this->chunkSizeBytes;
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function getDatabaseName()
+    {
+        return $this->databaseName;
+    }
+
+    /**
+     * Gets the file document of the GridFS file associated with a stream.
+     *
+     * @param resource $stream GridFS stream
+     * @return array|object
+     * @throws InvalidArgumentException if $stream is not a GridFS stream
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function getFileDocumentForStream($stream)
+    {
+        $file = $this->getRawFileDocumentForStream($stream);
+
+        // Filter the raw document through the specified type map
+        return apply_type_map_to_document($file, $this->typeMap);
+    }
+
+    /**
+     * Gets the file document's ID of the GridFS file associated with a stream.
+     *
+     * @param resource $stream GridFS stream
+     * @return mixed
+     * @throws CorruptFileException if the file "_id" field does not exist
+     * @throws InvalidArgumentException if $stream is not a GridFS stream
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function getFileIdForStream($stream)
+    {
+        $file = $this->getRawFileDocumentForStream($stream);
+
+        /* Filter the raw document through the specified type map, but override
+         * the root type so we can reliably access the ID.
+         */
+        $typeMap = ['root' => 'stdClass'] + $this->typeMap;
+        $file = apply_type_map_to_document($file, $typeMap);
+        assert(is_object($file));
+
+        if (! isset($file->_id) && ! property_exists($file, '_id')) {
+            throw new CorruptFileException('file._id does not exist');
+        }
+
+        return $file->_id;
+    }
+
+    /**
+     * Return the files collection.
+     *
+     * @return Collection
+     */
+    public function getFilesCollection()
+    {
+        return $this->collectionWrapper->getFilesCollection();
+    }
+
+    /**
+     * Return the read concern for this GridFS bucket.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
+     * @return ReadConcern
+     */
+    public function getReadConcern()
+    {
+        return $this->readConcern;
+    }
+
+    /**
+     * Return the read preference for this GridFS bucket.
+     *
+     * @return ReadPreference
+     */
+    public function getReadPreference()
+    {
+        return $this->readPreference;
+    }
+
+    /**
+     * Return the type map for this GridFS bucket.
+     *
+     * @return array
+     */
+    public function getTypeMap()
+    {
+        return $this->typeMap;
+    }
+
+    /**
+     * Return the write concern for this GridFS bucket.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
+     * @return WriteConcern
+     */
+    public function getWriteConcern()
+    {
+        return $this->writeConcern;
+    }
+
+    /**
+     * Opens a readable stream for reading a GridFS file.
+     *
+     * @param mixed $id File ID
+     * @return resource
+     * @throws FileNotFoundException if no file could be selected
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function openDownloadStream($id)
+    {
+        $file = $this->collectionWrapper->findFileById($id);
+
+        if ($file === null) {
+            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+        }
+
+        return $this->openDownloadStreamByFile($file);
+    }
+
+    /**
+     * Opens a readable stream to read a GridFS file, which is selected
+     * by name and revision.
+     *
+     * Supported options:
+     *
+     *  * revision (integer): Which revision (i.e. documents with the same
+     *    filename and different uploadDate) of the file to retrieve. Defaults
+     *    to -1 (i.e. the most recent revision).
+     *
+     * Revision numbers are defined as follows:
+     *
+     *  * 0 = the original stored file
+     *  * 1 = the first revision
+     *  * 2 = the second revision
+     *  * etc…
+     *  * -2 = the second most recent revision
+     *  * -1 = the most recent revision
+     *
+     * @param string $filename Filename
+     * @param array  $options  Download options
+     * @return resource
+     * @throws FileNotFoundException if no file could be selected
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function openDownloadStreamByName(string $filename, array $options = [])
+    {
+        $options += ['revision' => -1];
+
+        $file = $this->collectionWrapper->findFileByFilenameAndRevision($filename, $options['revision']);
+
+        if ($file === null) {
+            throw FileNotFoundException::byFilenameAndRevision($filename, $options['revision'], $this->getFilesNamespace());
+        }
+
+        return $this->openDownloadStreamByFile($file);
+    }
+
+    /**
+     * Opens a writable stream for writing a GridFS file.
+     *
+     * Supported options:
+     *
+     *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
+     *
+     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
+     *    bucket's chunk size.
+     *
+     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
+     *    the stored file. Defaults to "false".
+     *
+     *  * metadata (document): User data for the "metadata" field of the files
+     *    collection document.
+     *
+     * @param string $filename Filename
+     * @param array  $options  Upload options
+     * @return resource
+     */
+    public function openUploadStream(string $filename, array $options = [])
+    {
+        $options += [
+            'chunkSizeBytes' => $this->chunkSizeBytes,
+            'disableMD5' => $this->disableMD5,
+        ];
+
+        $path = $this->createPathForUpload();
+        $context = stream_context_create([
+            self::STREAM_WRAPPER_PROTOCOL => [
+                'collectionWrapper' => $this->collectionWrapper,
+                'filename' => $filename,
+                'options' => $options,
+            ],
+        ]);
+
+        return fopen($path, 'w', false, $context);
+    }
+
+    /**
+     * Renames the GridFS file with the specified ID.
+     *
+     * @param mixed  $id          File ID
+     * @param string $newFilename New filename
+     * @throws FileNotFoundException if no file could be selected
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function rename($id, string $newFilename)
+    {
+        $updateResult = $this->collectionWrapper->updateFilenameForId($id, $newFilename);
+
+        if ($updateResult->getModifiedCount() === 1) {
+            return;
+        }
+
+        /* If the update resulted in no modification, it's possible that the
+         * file did not exist, in which case we must raise an error. Checking
+         * the write result's matched count will be most efficient, but fall
+         * back to a findOne operation if necessary (i.e. legacy writes).
+         */
+        $found = $updateResult->getMatchedCount() !== null
+            ? $updateResult->getMatchedCount() === 1
+            : $this->collectionWrapper->findFileById($id) !== null;
+
+        if (! $found) {
+            throw FileNotFoundException::byId($id, $this->getFilesNamespace());
+        }
+    }
+
+    /**
+     * Writes the contents of a readable stream to a GridFS file.
+     *
+     * Supported options:
+     *
+     *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
+     *
+     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the
+     *    bucket's chunk size.
+     *
+     *  * disableMD5 (boolean): When true, no MD5 sum will be generated for
+     *    the stored file. Defaults to "false".
+     *
+     *  * metadata (document): User data for the "metadata" field of the files
+     *    collection document.
+     *
+     * @param string   $filename Filename
+     * @param resource $source   Readable stream
+     * @param array    $options  Stream options
+     * @return mixed ID of the newly created GridFS file
+     * @throws InvalidArgumentException if $source is not a GridFS stream
+     * @throws StreamException if the file could not be uploaded
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function uploadFromStream(string $filename, $source, array $options = [])
+    {
+        if (! is_resource($source) || get_resource_type($source) != "stream") {
+            throw InvalidArgumentException::invalidType('$source', $source, 'resource');
+        }
+
+        $destination = $this->openUploadStream($filename, $options);
+
+        if (@stream_copy_to_stream($source, $destination) === false) {
+            $destinationUri = $this->createPathForFile($this->getRawFileDocumentForStream($destination));
+
+            throw StreamException::uploadFailed($filename, $source, $destinationUri);
+        }
+
+        return $this->getFileIdForStream($destination);
+    }
+
+    /**
+     * Creates a path for an existing GridFS file.
+     *
+     * @param object $file GridFS file document
+     */
+    private function createPathForFile(object $file): string
+    {
+        if (is_array($file->_id) || (is_object($file->_id) && ! method_exists($file->_id, '__toString'))) {
+            $id = toJSON(fromPHP(['_id' => $file->_id]));
+        } else {
+            $id = (string) $file->_id;
+        }
+
+        return sprintf(
+            '%s://%s/%s.files/%s',
+            self::STREAM_WRAPPER_PROTOCOL,
+            urlencode($this->databaseName),
+            urlencode($this->bucketName),
+            urlencode($id)
+        );
+    }
+
+    /**
+     * Creates a path for a new GridFS file, which does not yet have an ID.
+     */
+    private function createPathForUpload(): string
+    {
+        return sprintf(
+            '%s://%s/%s.files',
+            self::STREAM_WRAPPER_PROTOCOL,
+            urlencode($this->databaseName),
+            urlencode($this->bucketName)
+        );
+    }
+
+    /**
+     * Returns the names of the files collection.
+     */
+    private function getFilesNamespace(): string
+    {
+        return sprintf('%s.%s.files', $this->databaseName, $this->bucketName);
+    }
+
+    /**
+     * Gets the file document of the GridFS file associated with a stream.
+     *
+     * This returns the raw document from the StreamWrapper, which does not
+     * respect the Bucket's type map.
+     *
+     * @param resource $stream GridFS stream
+     * @throws InvalidArgumentException
+     */
+    private function getRawFileDocumentForStream($stream): object
+    {
+        if (! is_resource($stream) || get_resource_type($stream) != "stream") {
+            throw InvalidArgumentException::invalidType('$stream', $stream, 'resource');
+        }
+
+        $metadata = stream_get_meta_data($stream);
+
+        if (! isset($metadata['wrapper_data']) || ! $metadata['wrapper_data'] instanceof StreamWrapper) {
+            throw InvalidArgumentException::invalidType('$stream wrapper data', $metadata['wrapper_data'] ?? null, StreamWrapper::class);
+        }
+
+        return $metadata['wrapper_data']->getFile();
+    }
+
+    /**
+     * Opens a readable stream for the GridFS file.
+     *
+     * @param object $file GridFS file document
+     * @return resource
+     */
+    private function openDownloadStreamByFile(object $file)
+    {
+        $path = $this->createPathForFile($file);
+        $context = stream_context_create([
+            self::STREAM_WRAPPER_PROTOCOL => [
+                'collectionWrapper' => $this->collectionWrapper,
+                'file' => $file,
+            ],
+        ]);
+
+        return fopen($path, 'r', false, $context);
+    }
+
+    /**
+     * Registers the GridFS stream wrapper if it is not already registered.
+     */
+    private function registerStreamWrapper(): void
+    {
+        if (in_array(self::STREAM_WRAPPER_PROTOCOL, stream_get_wrappers())) {
+            return;
+        }
+
+        StreamWrapper::register(self::STREAM_WRAPPER_PROTOCOL);
+    }
+}

+ 367 - 0
api/v1/vendor/mongodb/mongodb/src/GridFS/CollectionWrapper.php

@@ -0,0 +1,367 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use ArrayIterator;
+use MongoDB\Collection;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Manager;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\UpdateResult;
+use MultipleIterator;
+
+use function abs;
+use function assert;
+use function count;
+use function is_numeric;
+use function is_object;
+use function sprintf;
+
+/**
+ * CollectionWrapper abstracts the GridFS files and chunks collections.
+ *
+ * @internal
+ */
+class CollectionWrapper
+{
+    /** @var string */
+    private $bucketName;
+
+    /** @var Collection */
+    private $chunksCollection;
+
+    /** @var string */
+    private $databaseName;
+
+    /** @var boolean */
+    private $checkedIndexes = false;
+
+    /** @var Collection */
+    private $filesCollection;
+
+    /**
+     * Constructs a GridFS collection wrapper.
+     *
+     * @see Collection::__construct() for supported options
+     * @param Manager $manager           Manager instance from the driver
+     * @param string  $databaseName      Database name
+     * @param string  $bucketName        Bucket name
+     * @param array   $collectionOptions Collection options
+     * @throws InvalidArgumentException
+     */
+    public function __construct(Manager $manager, string $databaseName, string $bucketName, array $collectionOptions = [])
+    {
+        $this->databaseName = $databaseName;
+        $this->bucketName = $bucketName;
+
+        $this->filesCollection = new Collection($manager, $databaseName, sprintf('%s.files', $bucketName), $collectionOptions);
+        $this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions);
+    }
+
+    /**
+     * Deletes all GridFS chunks for a given file ID.
+     *
+     * @param mixed $id
+     */
+    public function deleteChunksByFilesId($id): void
+    {
+        $this->chunksCollection->deleteMany(['files_id' => $id]);
+    }
+
+    /**
+     * Deletes a GridFS file and related chunks by ID.
+     *
+     * @param mixed $id
+     */
+    public function deleteFileAndChunksById($id): void
+    {
+        $this->filesCollection->deleteOne(['_id' => $id]);
+        $this->chunksCollection->deleteMany(['files_id' => $id]);
+    }
+
+    /**
+     * Drops the GridFS files and chunks collections.
+     */
+    public function dropCollections(): void
+    {
+        $this->filesCollection->drop(['typeMap' => []]);
+        $this->chunksCollection->drop(['typeMap' => []]);
+    }
+
+    /**
+     * Finds GridFS chunk documents for a given file ID and optional offset.
+     *
+     * @param mixed   $id        File ID
+     * @param integer $fromChunk Starting chunk (inclusive)
+     */
+    public function findChunksByFileId($id, int $fromChunk = 0): Cursor
+    {
+        return $this->chunksCollection->find(
+            [
+                'files_id' => $id,
+                'n' => ['$gte' => $fromChunk],
+            ],
+            [
+                'sort' => ['n' => 1],
+                'typeMap' => ['root' => 'stdClass'],
+            ]
+        );
+    }
+
+    /**
+     * Finds a GridFS file document for a given filename and revision.
+     *
+     * Revision numbers are defined as follows:
+     *
+     *  * 0 = the original stored file
+     *  * 1 = the first revision
+     *  * 2 = the second revision
+     *  * etc…
+     *  * -2 = the second most recent revision
+     *  * -1 = the most recent revision
+     *
+     * @see Bucket::downloadToStreamByName()
+     * @see Bucket::openDownloadStreamByName()
+     */
+    public function findFileByFilenameAndRevision(string $filename, int $revision): ?object
+    {
+        $filename = $filename;
+        $revision = $revision;
+
+        if ($revision < 0) {
+            $skip = abs($revision) - 1;
+            $sortOrder = -1;
+        } else {
+            $skip = $revision;
+            $sortOrder = 1;
+        }
+
+        $file = $this->filesCollection->findOne(
+            ['filename' => $filename],
+            [
+                'skip' => $skip,
+                'sort' => ['uploadDate' => $sortOrder],
+                'typeMap' => ['root' => 'stdClass'],
+            ]
+        );
+        assert(is_object($file) || $file === null);
+
+        return $file;
+    }
+
+    /**
+     * Finds a GridFS file document for a given ID.
+     *
+     * @param mixed $id
+     */
+    public function findFileById($id): ?object
+    {
+        $file = $this->filesCollection->findOne(
+            ['_id' => $id],
+            ['typeMap' => ['root' => 'stdClass']]
+        );
+        assert(is_object($file) || $file === null);
+
+        return $file;
+    }
+
+    /**
+     * Finds documents from the GridFS bucket's files collection.
+     *
+     * @see Find::__construct() for supported options
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return Cursor
+     */
+    public function findFiles($filter, array $options = [])
+    {
+        return $this->filesCollection->find($filter, $options);
+    }
+
+    /**
+     * Finds a single document from the GridFS bucket's files collection.
+     *
+     * @param array|object $filter  Query by which to filter documents
+     * @param array        $options Additional options
+     * @return array|object|null
+     */
+    public function findOneFile($filter, array $options = [])
+    {
+        return $this->filesCollection->findOne($filter, $options);
+    }
+
+    public function getBucketName(): string
+    {
+        return $this->bucketName;
+    }
+
+    public function getChunksCollection(): Collection
+    {
+        return $this->chunksCollection;
+    }
+
+    public function getDatabaseName(): string
+    {
+        return $this->databaseName;
+    }
+
+    public function getFilesCollection(): Collection
+    {
+        return $this->filesCollection;
+    }
+
+    /**
+     * Inserts a document into the chunks collection.
+     *
+     * @param array|object $chunk Chunk document
+     */
+    public function insertChunk($chunk): void
+    {
+        if (! $this->checkedIndexes) {
+            $this->ensureIndexes();
+        }
+
+        $this->chunksCollection->insertOne($chunk);
+    }
+
+    /**
+     * Inserts a document into the files collection.
+     *
+     * The file document should be inserted after all chunks have been inserted.
+     *
+     * @param array|object $file File document
+     */
+    public function insertFile($file): void
+    {
+        if (! $this->checkedIndexes) {
+            $this->ensureIndexes();
+        }
+
+        $this->filesCollection->insertOne($file);
+    }
+
+    /**
+     * Updates the filename field in the file document for a given ID.
+     *
+     * @param mixed $id
+     */
+    public function updateFilenameForId($id, string $filename): UpdateResult
+    {
+        return $this->filesCollection->updateOne(
+            ['_id' => $id],
+            ['$set' => ['filename' => $filename]]
+        );
+    }
+
+    /**
+     * Create an index on the chunks collection if it does not already exist.
+     */
+    private function ensureChunksIndex(): void
+    {
+        $expectedIndex = ['files_id' => 1, 'n' => 1];
+
+        foreach ($this->chunksCollection->listIndexes() as $index) {
+            if ($index->isUnique() && $this->indexKeysMatch($expectedIndex, $index->getKey())) {
+                return;
+            }
+        }
+
+        $this->chunksCollection->createIndex($expectedIndex, ['unique' => true]);
+    }
+
+    /**
+     * Create an index on the files collection if it does not already exist.
+     */
+    private function ensureFilesIndex(): void
+    {
+        $expectedIndex = ['filename' => 1, 'uploadDate' => 1];
+
+        foreach ($this->filesCollection->listIndexes() as $index) {
+            if ($this->indexKeysMatch($expectedIndex, $index->getKey())) {
+                return;
+            }
+        }
+
+        $this->filesCollection->createIndex($expectedIndex);
+    }
+
+    /**
+     * Ensure indexes on the files and chunks collections exist.
+     *
+     * This method is called once before the first write operation on a GridFS
+     * bucket. Indexes are only be created if the files collection is empty.
+     */
+    private function ensureIndexes(): void
+    {
+        if ($this->checkedIndexes) {
+            return;
+        }
+
+        $this->checkedIndexes = true;
+
+        if (! $this->isFilesCollectionEmpty()) {
+            return;
+        }
+
+        $this->ensureFilesIndex();
+        $this->ensureChunksIndex();
+    }
+
+    private function indexKeysMatch(array $expectedKeys, array $actualKeys): bool
+    {
+        if (count($expectedKeys) !== count($actualKeys)) {
+            return false;
+        }
+
+        $iterator = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
+        $iterator->attachIterator(new ArrayIterator($expectedKeys));
+        $iterator->attachIterator(new ArrayIterator($actualKeys));
+
+        foreach ($iterator as $key => $value) {
+            [$expectedKey, $actualKey]     = $key;
+            [$expectedValue, $actualValue] = $value;
+
+            if ($expectedKey !== $actualKey) {
+                return false;
+            }
+
+            /* Since we don't expect special indexes (e.g. text), we mark any
+             * index with a non-numeric definition as unequal. All others are
+             * compared against their int value to avoid differences due to
+             * some drivers using float values in the key specification. */
+            if (! is_numeric($actualValue) || (int) $expectedValue !== (int) $actualValue) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns whether the files collection is empty.
+     */
+    private function isFilesCollectionEmpty(): bool
+    {
+        return null === $this->filesCollection->findOne([], [
+            'readPreference' => new ReadPreference(ReadPreference::PRIMARY),
+            'projection' => ['_id' => 1],
+            'typeMap' => [],
+        ]);
+    }
+}

+ 68 - 0
api/v1/vendor/mongodb/mongodb/src/GridFS/Exception/CorruptFileException.php

@@ -0,0 +1,68 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS\Exception;
+
+use MongoDB\Exception\RuntimeException;
+
+use function sprintf;
+
+class CorruptFileException extends RuntimeException
+{
+    /**
+     * Thrown when a chunk doesn't contain valid data.
+     */
+    public static function invalidChunkData(int $chunkIndex): self
+    {
+        return new static(sprintf('Invalid data found for index "%d"', $chunkIndex));
+    }
+
+    /**
+     * Thrown when a chunk is not found for an expected index.
+     *
+     * @param integer $expectedIndex Expected index number
+     * @return self
+     */
+    public static function missingChunk(int $expectedIndex)
+    {
+        return new static(sprintf('Chunk not found for index "%d"', $expectedIndex));
+    }
+
+    /**
+     * Thrown when a chunk has an unexpected index number.
+     *
+     * @param integer $index         Actual index number (i.e. "n" field)
+     * @param integer $expectedIndex Expected index number
+     * @return self
+     */
+    public static function unexpectedIndex(int $index, int $expectedIndex)
+    {
+        return new static(sprintf('Expected chunk to have index "%d" but found "%d"', $expectedIndex, $index));
+    }
+
+    /**
+     * Thrown when a chunk has an unexpected data size.
+     *
+     * @param integer $size         Actual size (i.e. "data" field length)
+     * @param integer $expectedSize Expected size
+     * @return self
+     */
+    public static function unexpectedSize(int $size, int $expectedSize)
+    {
+        return new static(sprintf('Expected chunk to have size "%d" but found "%d"', $expectedSize, $size));
+    }
+}

+ 54 - 0
api/v1/vendor/mongodb/mongodb/src/GridFS/Exception/FileNotFoundException.php

@@ -0,0 +1,54 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS\Exception;
+
+use MongoDB\Exception\RuntimeException;
+
+use function MongoDB\BSON\fromPHP;
+use function MongoDB\BSON\toJSON;
+use function sprintf;
+
+class FileNotFoundException extends RuntimeException
+{
+    /**
+     * Thrown when a file cannot be found by its filename and revision.
+     *
+     * @param string  $filename  Filename
+     * @param integer $revision  Revision
+     * @param string  $namespace Namespace for the files collection
+     * @return self
+     */
+    public static function byFilenameAndRevision(string $filename, int $revision, string $namespace)
+    {
+        return new static(sprintf('File with name "%s" and revision "%d" not found in "%s"', $filename, $revision, $namespace));
+    }
+
+    /**
+     * Thrown when a file cannot be found by its ID.
+     *
+     * @param mixed  $id        File ID
+     * @param string $namespace Namespace for the files collection
+     * @return self
+     */
+    public static function byId($id, string $namespace)
+    {
+        $json = toJSON(fromPHP(['_id' => $id]));
+
+        return new static(sprintf('File "%s" not found in "%s"', $json, $namespace));
+    }
+}

+ 47 - 0
api/v1/vendor/mongodb/mongodb/src/GridFS/Exception/StreamException.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace MongoDB\GridFS\Exception;
+
+use MongoDB\Exception\RuntimeException;
+
+use function MongoDB\BSON\fromPHP;
+use function MongoDB\BSON\toJSON;
+use function sprintf;
+use function stream_get_meta_data;
+
+class StreamException extends RuntimeException
+{
+    /**
+     * @param resource $source
+     * @param resource $destination
+     */
+    public static function downloadFromFilenameFailed(string $filename, $source, $destination): self
+    {
+        $sourceMetadata = stream_get_meta_data($source);
+        $destinationMetadata = stream_get_meta_data($destination);
+
+        return new static(sprintf('Downloading file from "%s" to "%s" failed. GridFS filename: "%s"', $sourceMetadata['uri'], $destinationMetadata['uri'], $filename));
+    }
+
+    /**
+     * @param mixed    $id
+     * @param resource $source
+     * @param resource $destination
+     */
+    public static function downloadFromIdFailed($id, $source, $destination): self
+    {
+        $idString = toJSON(fromPHP(['_id' => $id]));
+        $sourceMetadata = stream_get_meta_data($source);
+        $destinationMetadata = stream_get_meta_data($destination);
+
+        return new static(sprintf('Downloading file from "%s" to "%s" failed. GridFS identifier: "%s"', $sourceMetadata['uri'], $destinationMetadata['uri'], $idString));
+    }
+
+    /** @param resource $source */
+    public static function uploadFailed(string $filename, $source, string $destinationUri): self
+    {
+        $sourceMetadata = stream_get_meta_data($source);
+
+        return new static(sprintf('Uploading file from "%s" to "%s" failed. GridFS filename: "%s"', $sourceMetadata['uri'], $destinationUri, $filename));
+    }
+}

+ 323 - 0
api/v1/vendor/mongodb/mongodb/src/GridFS/ReadableStream.php

@@ -0,0 +1,323 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use MongoDB\BSON\Binary;
+use MongoDB\Driver\Cursor;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\GridFS\Exception\CorruptFileException;
+
+use function assert;
+use function ceil;
+use function floor;
+use function is_integer;
+use function is_object;
+use function property_exists;
+use function sprintf;
+use function strlen;
+use function substr;
+
+/**
+ * ReadableStream abstracts the process of reading a GridFS file.
+ *
+ * @internal
+ */
+class ReadableStream
+{
+    /** @var string|null */
+    private $buffer;
+
+    /** @var integer */
+    private $bufferOffset = 0;
+
+    /** @var integer */
+    private $chunkSize;
+
+    /** @var integer */
+    private $chunkOffset = 0;
+
+    /** @var Cursor|null */
+    private $chunksIterator;
+
+    /** @var CollectionWrapper */
+    private $collectionWrapper;
+
+    /** @var integer */
+    private $expectedLastChunkSize = 0;
+
+    /** @var object */
+    private $file;
+
+    /** @var integer */
+    private $length;
+
+    /** @var integer */
+    private $numChunks = 0;
+
+    /**
+     * Constructs a readable GridFS stream.
+     *
+     * @param CollectionWrapper $collectionWrapper GridFS collection wrapper
+     * @param object            $file              GridFS file document
+     * @throws CorruptFileException
+     */
+    public function __construct(CollectionWrapper $collectionWrapper, object $file)
+    {
+        if (! isset($file->chunkSize) || ! is_integer($file->chunkSize) || $file->chunkSize < 1) {
+            throw new CorruptFileException('file.chunkSize is not an integer >= 1');
+        }
+
+        if (! isset($file->length) || ! is_integer($file->length) || $file->length < 0) {
+            throw new CorruptFileException('file.length is not an integer > 0');
+        }
+
+        if (! isset($file->_id) && ! property_exists($file, '_id')) {
+            throw new CorruptFileException('file._id does not exist');
+        }
+
+        $this->file = $file;
+        $this->chunkSize = $file->chunkSize;
+        $this->length = $file->length;
+
+        $this->collectionWrapper = $collectionWrapper;
+
+        if ($this->length > 0) {
+            $this->numChunks = (integer) ceil($this->length / $this->chunkSize);
+            $this->expectedLastChunkSize = $this->length - (($this->numChunks - 1) * $this->chunkSize);
+        }
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo(): array
+    {
+        return [
+            'bucketName' => $this->collectionWrapper->getBucketName(),
+            'databaseName' => $this->collectionWrapper->getDatabaseName(),
+            'file' => $this->file,
+        ];
+    }
+
+    public function close(): void
+    {
+        // Nothing to do
+    }
+
+    public function getFile(): object
+    {
+        return $this->file;
+    }
+
+    public function getSize(): int
+    {
+        return $this->length;
+    }
+
+    /**
+     * Return whether the current read position is at the end of the stream.
+     */
+    public function isEOF(): bool
+    {
+        if ($this->chunkOffset === $this->numChunks - 1) {
+            return $this->bufferOffset >= $this->expectedLastChunkSize;
+        }
+
+        return $this->chunkOffset >= $this->numChunks;
+    }
+
+    /**
+     * Read bytes from the stream.
+     *
+     * Note: this method may return a string smaller than the requested length
+     * if data is not available to be read.
+     *
+     * @param integer $length Number of bytes to read
+     * @throws InvalidArgumentException if $length is negative
+     */
+    public function readBytes(int $length): string
+    {
+        if ($length < 0) {
+            throw new InvalidArgumentException(sprintf('$length must be >= 0; given: %d', $length));
+        }
+
+        if ($this->chunksIterator === null) {
+            $this->initChunksIterator();
+        }
+
+        if ($this->buffer === null && ! $this->initBufferFromCurrentChunk()) {
+            return '';
+        }
+
+        assert($this->buffer !== null);
+
+        $data = '';
+
+        while (strlen($data) < $length) {
+            if ($this->bufferOffset >= strlen($this->buffer) && ! $this->initBufferFromNextChunk()) {
+                break;
+            }
+
+            $initialDataLength = strlen($data);
+            $data .= substr($this->buffer, $this->bufferOffset, $length - $initialDataLength);
+            $this->bufferOffset += strlen($data) - $initialDataLength;
+        }
+
+        return $data;
+    }
+
+    /**
+     * Seeks the chunk and buffer offsets for the next read operation.
+     *
+     * @throws InvalidArgumentException if $offset is out of range
+     */
+    public function seek(int $offset): void
+    {
+        if ($offset < 0 || $offset > $this->file->length) {
+            throw new InvalidArgumentException(sprintf('$offset must be >= 0 and <= %d; given: %d', $this->file->length, $offset));
+        }
+
+        /* Compute the offsets for the chunk and buffer (i.e. chunk data) from
+         * which we will expect to read after seeking. If the chunk offset
+         * changed, we'll also need to reset the buffer.
+         */
+        $lastChunkOffset = $this->chunkOffset;
+        $this->chunkOffset = (integer) floor($offset / $this->chunkSize);
+        $this->bufferOffset = $offset % $this->chunkSize;
+
+        if ($lastChunkOffset === $this->chunkOffset) {
+            return;
+        }
+
+        if ($this->chunksIterator === null) {
+            return;
+        }
+
+        // Clear the buffer since the current chunk will be changed
+        $this->buffer = null;
+
+        /* If we are seeking to a previous chunk, we need to reinitialize the
+         * chunk iterator.
+         */
+        if ($lastChunkOffset > $this->chunkOffset) {
+            $this->chunksIterator = null;
+
+            return;
+        }
+
+        /* If we are seeking to a subsequent chunk, we do not need to
+         * reinitalize the chunk iterator. Instead, we can move forward
+         * to $this->chunkOffset.
+         */
+        $numChunks = $this->chunkOffset - $lastChunkOffset;
+        for ($i = 0; $i < $numChunks; $i++) {
+            $this->chunksIterator->next();
+        }
+    }
+
+    /**
+     * Return the current position of the stream.
+     *
+     * This is the offset within the stream where the next byte would be read.
+     */
+    public function tell(): int
+    {
+        return ($this->chunkOffset * $this->chunkSize) + $this->bufferOffset;
+    }
+
+    /**
+     * Initialize the buffer to the current chunk's data.
+     *
+     * @return boolean Whether there was a current chunk to read
+     * @throws CorruptFileException if an expected chunk could not be read successfully
+     */
+    private function initBufferFromCurrentChunk(): bool
+    {
+        if ($this->chunkOffset === 0 && $this->numChunks === 0) {
+            return false;
+        }
+
+        if ($this->chunksIterator === null) {
+            return false;
+        }
+
+        if (! $this->chunksIterator->valid()) {
+            throw CorruptFileException::missingChunk($this->chunkOffset);
+        }
+
+        $currentChunk = $this->chunksIterator->current();
+        assert(is_object($currentChunk));
+
+        if ($currentChunk->n !== $this->chunkOffset) {
+            throw CorruptFileException::unexpectedIndex($currentChunk->n, $this->chunkOffset);
+        }
+
+        if (! $currentChunk->data instanceof Binary) {
+            throw CorruptFileException::invalidChunkData($this->chunkOffset);
+        }
+
+        $this->buffer = $currentChunk->data->getData();
+
+        $actualChunkSize = strlen($this->buffer);
+
+        $expectedChunkSize = $this->chunkOffset === $this->numChunks - 1
+            ? $this->expectedLastChunkSize
+            : $this->chunkSize;
+
+        if ($actualChunkSize !== $expectedChunkSize) {
+            throw CorruptFileException::unexpectedSize($actualChunkSize, $expectedChunkSize);
+        }
+
+        return true;
+    }
+
+    /**
+     * Advance to the next chunk and initialize the buffer to its data.
+     *
+     * @return boolean Whether there was a next chunk to read
+     * @throws CorruptFileException if an expected chunk could not be read successfully
+     */
+    private function initBufferFromNextChunk(): bool
+    {
+        if ($this->chunkOffset === $this->numChunks - 1) {
+            return false;
+        }
+
+        if ($this->chunksIterator === null) {
+            return false;
+        }
+
+        $this->bufferOffset = 0;
+        $this->chunkOffset++;
+        $this->chunksIterator->next();
+
+        return $this->initBufferFromCurrentChunk();
+    }
+
+    /**
+     * Initializes the chunk iterator starting from the current offset.
+     */
+    private function initChunksIterator(): void
+    {
+        $this->chunksIterator = $this->collectionWrapper->findChunksByFileId($this->file->_id, $this->chunkOffset);
+        $this->chunksIterator->rewind();
+    }
+}

+ 326 - 0
api/v1/vendor/mongodb/mongodb/src/GridFS/StreamWrapper.php

@@ -0,0 +1,326 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use MongoDB\BSON\UTCDateTime;
+
+use function assert;
+use function explode;
+use function in_array;
+use function is_integer;
+use function is_resource;
+use function stream_context_get_options;
+use function stream_get_wrappers;
+use function stream_wrapper_register;
+use function stream_wrapper_unregister;
+
+use const SEEK_CUR;
+use const SEEK_END;
+use const SEEK_SET;
+use const STREAM_IS_URL;
+
+/**
+ * Stream wrapper for reading and writing a GridFS file.
+ *
+ * @internal
+ * @see Bucket::openUploadStream()
+ * @see Bucket::openDownloadStream()
+ */
+class StreamWrapper
+{
+    /** @var resource|null Stream context (set by PHP) */
+    public $context;
+
+    /** @var string|null */
+    private $protocol;
+
+    /** @var ReadableStream|WritableStream|null */
+    private $stream;
+
+    public function __destruct()
+    {
+        /* This destructor is a workaround for PHP trying to use the stream well
+         * after all objects have been destructed. This can cause autoloading
+         * issues and possibly segmentation faults during PHP shutdown. */
+        $this->stream = null;
+    }
+
+    /**
+     * Return the stream's file document.
+     */
+    public function getFile(): object
+    {
+        assert($this->stream !== null);
+
+        return $this->stream->getFile();
+    }
+
+    /**
+     * Register the GridFS stream wrapper.
+     *
+     * @param string $protocol Protocol to use for stream_wrapper_register()
+     */
+    public static function register(string $protocol = 'gridfs'): void
+    {
+        if (in_array($protocol, stream_get_wrappers())) {
+            stream_wrapper_unregister($protocol);
+        }
+
+        stream_wrapper_register($protocol, static::class, STREAM_IS_URL);
+    }
+
+    /**
+     * Closes the stream.
+     *
+     * @see https://php.net/manual/en/streamwrapper.stream-close.php
+     */
+    public function stream_close(): void
+    {
+        if (! $this->stream) {
+            return;
+        }
+
+        $this->stream->close();
+    }
+
+    /**
+     * Returns whether the file pointer is at the end of the stream.
+     *
+     * @see https://php.net/manual/en/streamwrapper.stream-eof.php
+     */
+    public function stream_eof(): bool
+    {
+        if (! $this->stream instanceof ReadableStream) {
+            return false;
+        }
+
+        return $this->stream->isEOF();
+    }
+
+    /**
+     * Opens the stream.
+     *
+     * @see https://php.net/manual/en/streamwrapper.stream-open.php
+     * @param string      $path       Path to the file resource
+     * @param string      $mode       Mode used to open the file (only "r" and "w" are supported)
+     * @param integer     $options    Additional flags set by the streams API
+     * @param string|null $openedPath Not used
+     */
+    public function stream_open(string $path, string $mode, int $options, ?string &$openedPath): bool
+    {
+        $this->initProtocol($path);
+
+        if ($mode === 'r') {
+            return $this->initReadableStream();
+        }
+
+        if ($mode === 'w') {
+            return $this->initWritableStream();
+        }
+
+        return false;
+    }
+
+    /**
+     * Read bytes from the stream.
+     *
+     * Note: this method may return a string smaller than the requested length
+     * if data is not available to be read.
+     *
+     * @see https://php.net/manual/en/streamwrapper.stream-read.php
+     * @param integer $length Number of bytes to read
+     */
+    public function stream_read(int $length): string
+    {
+        if (! $this->stream instanceof ReadableStream) {
+            return '';
+        }
+
+        return $this->stream->readBytes($length);
+    }
+
+    /**
+     * Return the current position of the stream.
+     *
+     * @see https://php.net/manual/en/streamwrapper.stream-seek.php
+     * @param integer $offset Stream offset to seek to
+     * @param integer $whence One of SEEK_SET, SEEK_CUR, or SEEK_END
+     * @return boolean True if the position was updated and false otherwise
+     */
+    public function stream_seek(int $offset, int $whence = SEEK_SET): bool
+    {
+        assert($this->stream !== null);
+
+        $size = $this->stream->getSize();
+
+        if ($whence === SEEK_CUR) {
+            $offset += $this->stream->tell();
+        }
+
+        if ($whence === SEEK_END) {
+            $offset += $size;
+        }
+
+        // WritableStreams are always positioned at the end of the stream
+        if ($this->stream instanceof WritableStream) {
+            return $offset === $size;
+        }
+
+        if ($offset < 0 || $offset > $size) {
+            return false;
+        }
+
+        $this->stream->seek($offset);
+
+        return true;
+    }
+
+    /**
+     * Return information about the stream.
+     *
+     * @see https://php.net/manual/en/streamwrapper.stream-stat.php
+     */
+    public function stream_stat(): array
+    {
+        assert($this->stream !== null);
+
+        $stat = $this->getStatTemplate();
+
+        $stat[2] = $stat['mode'] = $this->stream instanceof ReadableStream
+            ? 0100444  // S_IFREG & S_IRUSR & S_IRGRP & S_IROTH
+            : 0100222; // S_IFREG & S_IWUSR & S_IWGRP & S_IWOTH
+        $stat[7] = $stat['size'] = $this->stream->getSize();
+
+        $file = $this->stream->getFile();
+
+        if (isset($file->uploadDate) && $file->uploadDate instanceof UTCDateTime) {
+            $timestamp = $file->uploadDate->toDateTime()->getTimestamp();
+            $stat[9] = $stat['mtime'] = $timestamp;
+            $stat[10] = $stat['ctime'] = $timestamp;
+        }
+
+        if (isset($file->chunkSize) && is_integer($file->chunkSize)) {
+            $stat[11] = $stat['blksize'] = $file->chunkSize;
+        }
+
+        return $stat;
+    }
+
+    /**
+     * Return the current position of the stream.
+     *
+     * @see https://php.net/manual/en/streamwrapper.stream-tell.php
+     * @return integer The current position of the stream
+     */
+    public function stream_tell(): int
+    {
+        assert($this->stream !== null);
+
+        return $this->stream->tell();
+    }
+
+    /**
+     * Write bytes to the stream.
+     *
+     * @see https://php.net/manual/en/streamwrapper.stream-write.php
+     * @param string $data Data to write
+     * @return integer The number of bytes written
+     */
+    public function stream_write(string $data): int
+    {
+        if (! $this->stream instanceof WritableStream) {
+            return 0;
+        }
+
+        return $this->stream->writeBytes($data);
+    }
+
+    /**
+     * Returns a stat template with default values.
+     */
+    private function getStatTemplate(): array
+    {
+        return [
+            // phpcs:disable Squiz.Arrays.ArrayDeclaration.IndexNoNewline
+            0  => 0,  'dev'     => 0,
+            1  => 0,  'ino'     => 0,
+            2  => 0,  'mode'    => 0,
+            3  => 0,  'nlink'   => 0,
+            4  => 0,  'uid'     => 0,
+            5  => 0,  'gid'     => 0,
+            6  => -1, 'rdev'    => -1,
+            7  => 0,  'size'    => 0,
+            8  => 0,  'atime'   => 0,
+            9  => 0,  'mtime'   => 0,
+            10 => 0,  'ctime'   => 0,
+            11 => -1, 'blksize' => -1,
+            12 => -1, 'blocks'  => -1,
+            // phpcs:enable
+        ];
+    }
+
+    /**
+     * Initialize the protocol from the given path.
+     *
+     * @see StreamWrapper::stream_open()
+     */
+    private function initProtocol(string $path): void
+    {
+        $parts = explode('://', $path, 2);
+        $this->protocol = $parts[0] ?: 'gridfs';
+    }
+
+    /**
+     * Initialize the internal stream for reading.
+     *
+     * @see StreamWrapper::stream_open()
+     */
+    private function initReadableStream(): bool
+    {
+        assert(is_resource($this->context));
+        $context = stream_context_get_options($this->context);
+
+        assert($this->protocol !== null);
+        $this->stream = new ReadableStream(
+            $context[$this->protocol]['collectionWrapper'],
+            $context[$this->protocol]['file']
+        );
+
+        return true;
+    }
+
+    /**
+     * Initialize the internal stream for writing.
+     *
+     * @see StreamWrapper::stream_open()
+     */
+    private function initWritableStream(): bool
+    {
+        assert(is_resource($this->context));
+        $context = stream_context_get_options($this->context);
+
+        assert($this->protocol !== null);
+        $this->stream = new WritableStream(
+            $context[$this->protocol]['collectionWrapper'],
+            $context[$this->protocol]['filename'],
+            $context[$this->protocol]['options']
+        );
+
+        return true;
+    }
+}

+ 305 - 0
api/v1/vendor/mongodb/mongodb/src/GridFS/WritableStream.php

@@ -0,0 +1,305 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\GridFS;
+
+use HashContext;
+use MongoDB\BSON\Binary;
+use MongoDB\BSON\ObjectId;
+use MongoDB\BSON\UTCDateTime;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Exception\InvalidArgumentException;
+
+use function array_intersect_key;
+use function hash_final;
+use function hash_init;
+use function hash_update;
+use function is_array;
+use function is_bool;
+use function is_integer;
+use function is_object;
+use function is_string;
+use function MongoDB\is_string_array;
+use function sprintf;
+use function strlen;
+use function substr;
+
+/**
+ * WritableStream abstracts the process of writing a GridFS file.
+ *
+ * @internal
+ */
+class WritableStream
+{
+    private const DEFAULT_CHUNK_SIZE_BYTES = 261120;
+
+    /** @var string */
+    private $buffer = '';
+
+    /** @var integer */
+    private $chunkOffset = 0;
+
+    /** @var integer */
+    private $chunkSize;
+
+    /** @var boolean */
+    private $disableMD5;
+
+    /** @var CollectionWrapper */
+    private $collectionWrapper;
+
+    /** @var array */
+    private $file;
+
+    /** @var HashContext|null */
+    private $hashCtx;
+
+    /** @var boolean */
+    private $isClosed = false;
+
+    /** @var integer */
+    private $length = 0;
+
+    /**
+     * Constructs a writable GridFS stream.
+     *
+     * Supported options:
+     *
+     *  * _id (mixed): File document identifier. Defaults to a new ObjectId.
+     *
+     *  * aliases (array of strings): DEPRECATED An array of aliases.
+     *    Applications wishing to store aliases should add an aliases field to
+     *    the metadata document instead.
+     *
+     *  * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
+     *    261120 (i.e. 255 KiB).
+     *
+     *  * disableMD5 (boolean): When true, no MD5 sum will be generated.
+     *    Defaults to "false".
+     *
+     *  * contentType (string): DEPRECATED content type to be stored with the
+     *    file. This information should now be added to the metadata.
+     *
+     *  * metadata (document): User data for the "metadata" field of the files
+     *    collection document.
+     *
+     * @param CollectionWrapper $collectionWrapper GridFS collection wrapper
+     * @param string            $filename          Filename
+     * @param array             $options           Upload options
+     * @throws InvalidArgumentException
+     */
+    public function __construct(CollectionWrapper $collectionWrapper, string $filename, array $options = [])
+    {
+        $options += [
+            '_id' => new ObjectId(),
+            'chunkSizeBytes' => self::DEFAULT_CHUNK_SIZE_BYTES,
+            'disableMD5' => false,
+        ];
+
+        if (isset($options['aliases']) && ! is_string_array($options['aliases'])) {
+            throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings');
+        }
+
+        if (! is_integer($options['chunkSizeBytes'])) {
+            throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
+        }
+
+        if ($options['chunkSizeBytes'] < 1) {
+            throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes']));
+        }
+
+        if (! is_bool($options['disableMD5'])) {
+            throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean');
+        }
+
+        if (isset($options['contentType']) && ! is_string($options['contentType'])) {
+            throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string');
+        }
+
+        if (isset($options['metadata']) && ! is_array($options['metadata']) && ! is_object($options['metadata'])) {
+            throw InvalidArgumentException::invalidType('"metadata" option', $options['metadata'], 'array or object');
+        }
+
+        $this->chunkSize = $options['chunkSizeBytes'];
+        $this->collectionWrapper = $collectionWrapper;
+        $this->disableMD5 = $options['disableMD5'];
+
+        if (! $this->disableMD5) {
+            $this->hashCtx = hash_init('md5');
+        }
+
+        $this->file = [
+            '_id' => $options['_id'],
+            'chunkSize' => $this->chunkSize,
+            'filename' => $filename,
+        ] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]);
+    }
+
+    /**
+     * Return internal properties for debugging purposes.
+     *
+     * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
+     */
+    public function __debugInfo(): array
+    {
+        return [
+            'bucketName' => $this->collectionWrapper->getBucketName(),
+            'databaseName' => $this->collectionWrapper->getDatabaseName(),
+            'file' => $this->file,
+        ];
+    }
+
+    /**
+     * Closes an active stream and flushes all buffered data to GridFS.
+     */
+    public function close(): void
+    {
+        if ($this->isClosed) {
+            // TODO: Should this be an error condition? e.g. BadMethodCallException
+            return;
+        }
+
+        if (strlen($this->buffer) > 0) {
+            $this->insertChunkFromBuffer();
+        }
+
+        $this->fileCollectionInsert();
+        $this->isClosed = true;
+    }
+
+    /**
+     * Return the stream's file document.
+     */
+    public function getFile(): object
+    {
+        return (object) $this->file;
+    }
+
+    /**
+     * Return the stream's size in bytes.
+     *
+     * Note: this value will increase as more data is written to the stream.
+     */
+    public function getSize(): int
+    {
+        return $this->length + strlen($this->buffer);
+    }
+
+    /**
+     * Return the current position of the stream.
+     *
+     * This is the offset within the stream where the next byte would be
+     * written. Since seeking is not supported and writes are appended, this is
+     * always the end of the stream.
+     *
+     * @see WritableStream::getSize()
+     */
+    public function tell(): int
+    {
+        return $this->getSize();
+    }
+
+    /**
+     * Inserts binary data into GridFS via chunks.
+     *
+     * Data will be buffered internally until chunkSizeBytes are accumulated, at
+     * which point a chunk document will be inserted and the buffer reset.
+     *
+     * @param string $data Binary data to write
+     */
+    public function writeBytes(string $data): int
+    {
+        if ($this->isClosed) {
+            // TODO: Should this be an error condition? e.g. BadMethodCallException
+            return 0;
+        }
+
+        $bytesRead = 0;
+
+        while ($bytesRead != strlen($data)) {
+            $initialBufferLength = strlen($this->buffer);
+            $this->buffer .= substr($data, $bytesRead, $this->chunkSize - $initialBufferLength);
+            $bytesRead += strlen($this->buffer) - $initialBufferLength;
+
+            if (strlen($this->buffer) == $this->chunkSize) {
+                $this->insertChunkFromBuffer();
+            }
+        }
+
+        return $bytesRead;
+    }
+
+    private function abort(): void
+    {
+        try {
+            $this->collectionWrapper->deleteChunksByFilesId($this->file['_id']);
+        } catch (DriverRuntimeException $e) {
+            // We are already handling an error if abort() is called, so suppress this
+        }
+
+        $this->isClosed = true;
+    }
+
+    private function fileCollectionInsert(): void
+    {
+        $this->file['length'] = $this->length;
+        $this->file['uploadDate'] = new UTCDateTime();
+
+        if (! $this->disableMD5 && $this->hashCtx) {
+            $this->file['md5'] = hash_final($this->hashCtx);
+        }
+
+        try {
+            $this->collectionWrapper->insertFile($this->file);
+        } catch (DriverRuntimeException $e) {
+            $this->abort();
+
+            throw $e;
+        }
+    }
+
+    private function insertChunkFromBuffer(): void
+    {
+        if (strlen($this->buffer) == 0) {
+            return;
+        }
+
+        $data = $this->buffer;
+        $this->buffer = '';
+
+        $chunk = [
+            'files_id' => $this->file['_id'],
+            'n' => $this->chunkOffset,
+            'data' => new Binary($data, Binary::TYPE_GENERIC),
+        ];
+
+        if (! $this->disableMD5 && $this->hashCtx) {
+            hash_update($this->hashCtx, $data);
+        }
+
+        try {
+            $this->collectionWrapper->insertChunk($chunk);
+        } catch (DriverRuntimeException $e) {
+            $this->abort();
+
+            throw $e;
+        }
+
+        $this->length += strlen($data);
+        $this->chunkOffset++;
+    }
+}

+ 90 - 0
api/v1/vendor/mongodb/mongodb/src/InsertManyResult.php

@@ -0,0 +1,90 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\WriteResult;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Result class for a multi-document insert operation.
+ */
+class InsertManyResult
+{
+    /** @var WriteResult */
+    private $writeResult;
+
+    /** @var array */
+    private $insertedIds;
+
+    /** @var boolean */
+    private $isAcknowledged;
+
+    public function __construct(WriteResult $writeResult, array $insertedIds)
+    {
+        $this->writeResult = $writeResult;
+        $this->insertedIds = $insertedIds;
+        $this->isAcknowledged = $writeResult->isAcknowledged();
+    }
+
+    /**
+     * Return the number of documents that were inserted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see InsertManyResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getInsertedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getInsertedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return a map of the inserted documents' IDs.
+     *
+     * The index of each ID in the map corresponds to each document's position
+     * in the bulk operation. If a document had an ID prior to inserting (i.e.
+     * the driver did not generate an ID), the index will contain its "_id"
+     * field value. Any driver-generated ID will be a MongoDB\BSON\ObjectId
+     * instance.
+     *
+     * @return array
+     */
+    public function getInsertedIds()
+    {
+        return $this->insertedIds;
+    }
+
+    /**
+     * Return whether this insert result was acknowledged by the server.
+     *
+     * If the insert was not acknowledged, other fields from the WriteResult
+     * (e.g. insertedCount) will be undefined.
+     *
+     * @return boolean
+     */
+    public function isAcknowledged()
+    {
+        return $this->writeResult->isAcknowledged();
+    }
+}

+ 93 - 0
api/v1/vendor/mongodb/mongodb/src/InsertOneResult.php

@@ -0,0 +1,93 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use MongoDB\Driver\WriteResult;
+use MongoDB\Exception\BadMethodCallException;
+
+/**
+ * Result class for a single-document insert operation.
+ */
+class InsertOneResult
+{
+    /** @var WriteResult */
+    private $writeResult;
+
+    /** @var mixed */
+    private $insertedId;
+
+    /** @var boolean */
+    private $isAcknowledged;
+
+    /** @param mixed $insertedId */
+    public function __construct(WriteResult $writeResult, $insertedId)
+    {
+        $this->writeResult = $writeResult;
+        $this->insertedId = $insertedId;
+        $this->isAcknowledged = $writeResult->isAcknowledged();
+    }
+
+    /**
+     * Return the number of documents that were inserted.
+     *
+     * This method should only be called if the write was acknowledged.
+     *
+     * @see InsertOneResult::isAcknowledged()
+     * @return integer|null
+     * @throws BadMethodCallException if the write result is unacknowledged
+     */
+    public function getInsertedCount()
+    {
+        if ($this->isAcknowledged) {
+            return $this->writeResult->getInsertedCount();
+        }
+
+        throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__);
+    }
+
+    /**
+     * Return the inserted document's ID.
+     *
+     * If the document had an ID prior to inserting (i.e. the driver did not
+     * need to generate an ID), this will contain its "_id". Any
+     * driver-generated ID will be a MongoDB\BSON\ObjectId instance.
+     *
+     * @return mixed
+     */
+    public function getInsertedId()
+    {
+        return $this->insertedId;
+    }
+
+    /**
+     * Return whether this insert was acknowledged by the server.
+     *
+     * If the insert was not acknowledged, other fields from the WriteResult
+     * (e.g. insertedCount) will be undefined.
+     *
+     * If the insert was not acknowledged, other fields from the WriteResult
+     * (e.g. insertedCount) will be undefined and their getter methods should
+     * not be invoked.
+     *
+     * @return boolean
+     */
+    public function isAcknowledged()
+    {
+        return $this->writeResult->isAcknowledged();
+    }
+}

+ 108 - 0
api/v1/vendor/mongodb/mongodb/src/MapReduceResult.php

@@ -0,0 +1,108 @@
+<?php
+/*
+ * Copyright 2017-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB;
+
+use IteratorAggregate;
+use ReturnTypeWillChange;
+use stdClass;
+use Traversable;
+
+use function call_user_func;
+
+/**
+ * Result class for mapReduce command results.
+ *
+ * This class allows for iteration of mapReduce results irrespective of the
+ * output method (e.g. inline, collection) via the IteratorAggregate interface.
+ * It also provides access to command statistics.
+ *
+ * @see \MongoDB\Collection::mapReduce()
+ * @see https://mongodb.com/docs/manual/reference/command/mapReduce/
+ */
+class MapReduceResult implements IteratorAggregate
+{
+    /** @var callable */
+    private $getIterator;
+
+    /** @var integer */
+    private $executionTimeMS;
+
+    /** @var array */
+    private $counts;
+
+    /** @var array */
+    private $timing;
+
+    /**
+     * @internal
+     * @param callable $getIterator Callback that returns a Traversable for mapReduce results
+     * @param stdClass $result      Result document from the mapReduce command
+     */
+    public function __construct(callable $getIterator, stdClass $result)
+    {
+        $this->getIterator = $getIterator;
+        $this->executionTimeMS = isset($result->timeMillis) ? (integer) $result->timeMillis : 0;
+        $this->counts = isset($result->counts) ? (array) $result->counts : [];
+        $this->timing = isset($result->timing) ? (array) $result->timing : [];
+    }
+
+    /**
+     * Returns various count statistics from the mapReduce command.
+     *
+     * @return array
+     */
+    public function getCounts()
+    {
+        return $this->counts;
+    }
+
+    /**
+     * Return the command execution time in milliseconds.
+     *
+     * @return integer
+     */
+    public function getExecutionTimeMS()
+    {
+        return $this->executionTimeMS;
+    }
+
+    /**
+     * Return the mapReduce results as a Traversable.
+     *
+     * @see https://php.net/iteratoraggregate.getiterator
+     * @return Traversable
+     */
+    #[ReturnTypeWillChange]
+    public function getIterator()
+    {
+        return call_user_func($this->getIterator);
+    }
+
+    /**
+     * Returns various timing statistics from the mapReduce command.
+     *
+     * Note: timing statistics are only available if the mapReduce command's
+     * "verbose" option was true; otherwise, an empty array will be returned.
+     *
+     * @return array
+     */
+    public function getTiming()
+    {
+        return $this->timing;
+    }
+}

+ 103 - 0
api/v1/vendor/mongodb/mongodb/src/Model/BSONArray.php

@@ -0,0 +1,103 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use ArrayObject;
+use JsonSerializable;
+use MongoDB\BSON\Serializable;
+use MongoDB\BSON\Unserializable;
+use ReturnTypeWillChange;
+
+use function array_values;
+use function MongoDB\recursive_copy;
+
+/**
+ * Model class for a BSON array.
+ *
+ * The internal data will be filtered through array_values() during BSON
+ * serialization to ensure that it becomes a BSON array.
+ */
+class BSONArray extends ArrayObject implements JsonSerializable, Serializable, Unserializable
+{
+    /**
+     * Clone this BSONArray.
+     */
+    public function __clone()
+    {
+        foreach ($this as $key => $value) {
+            $this[$key] = recursive_copy($value);
+        }
+    }
+
+    /**
+     * Factory method for var_export().
+     *
+     * @see https://php.net/oop5.magic#object.set-state
+     * @see https://php.net/var-export
+     * @return self
+     */
+    public static function __set_state(array $properties)
+    {
+        $array = new static();
+        $array->exchangeArray($properties);
+
+        return $array;
+    }
+
+    /**
+     * Serialize the array to BSON.
+     *
+     * The array data will be numerically reindexed to ensure that it is stored
+     * as a BSON array.
+     *
+     * @see https://php.net/mongodb-bson-serializable.bsonserialize
+     * @return array
+     */
+    #[ReturnTypeWillChange]
+    public function bsonSerialize()
+    {
+        return array_values($this->getArrayCopy());
+    }
+
+    /**
+     * Unserialize the document to BSON.
+     *
+     * @see https://php.net/mongodb-bson-unserializable.bsonunserialize
+     * @param array $data Array data
+     */
+    #[ReturnTypeWillChange]
+    public function bsonUnserialize(array $data)
+    {
+        self::__construct($data);
+    }
+
+    /**
+     * Serialize the array to JSON.
+     *
+     * The array data will be numerically reindexed to ensure that it is stored
+     * as a JSON array.
+     *
+     * @see https://php.net/jsonserializable.jsonserialize
+     * @return array
+     */
+    #[ReturnTypeWillChange]
+    public function jsonSerialize()
+    {
+        return array_values($this->getArrayCopy());
+    }
+}

+ 107 - 0
api/v1/vendor/mongodb/mongodb/src/Model/BSONDocument.php

@@ -0,0 +1,107 @@
+<?php
+/*
+ * Copyright 2016-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use ArrayObject;
+use JsonSerializable;
+use MongoDB\BSON\Serializable;
+use MongoDB\BSON\Unserializable;
+use ReturnTypeWillChange;
+
+use function MongoDB\recursive_copy;
+
+/**
+ * Model class for a BSON document.
+ *
+ * The internal data will be cast to an object during BSON serialization to
+ * ensure that it becomes a BSON document.
+ */
+class BSONDocument extends ArrayObject implements JsonSerializable, Serializable, Unserializable
+{
+    /**
+     * Deep clone this BSONDocument.
+     */
+    public function __clone()
+    {
+        foreach ($this as $key => $value) {
+            $this[$key] = recursive_copy($value);
+        }
+    }
+
+    /**
+     * This overrides the parent constructor to allow property access of entries
+     * by default.
+     *
+     * @see https://php.net/arrayobject.construct
+     */
+    public function __construct(array $input = [], int $flags = ArrayObject::ARRAY_AS_PROPS, string $iteratorClass = 'ArrayIterator')
+    {
+        parent::__construct($input, $flags, $iteratorClass);
+    }
+
+    /**
+     * Factory method for var_export().
+     *
+     * @see https://php.net/oop5.magic#object.set-state
+     * @see https://php.net/var-export
+     * @return self
+     */
+    public static function __set_state(array $properties)
+    {
+        $document = new static();
+        $document->exchangeArray($properties);
+
+        return $document;
+    }
+
+    /**
+     * Serialize the document to BSON.
+     *
+     * @see https://php.net/mongodb-bson-serializable.bsonserialize
+     * @return object
+     */
+    #[ReturnTypeWillChange]
+    public function bsonSerialize()
+    {
+        return (object) $this->getArrayCopy();
+    }
+
+    /**
+     * Unserialize the document to BSON.
+     *
+     * @see https://php.net/mongodb-bson-unserializable.bsonunserialize
+     * @param array $data Array data
+     */
+    #[ReturnTypeWillChange]
+    public function bsonUnserialize(array $data)
+    {
+        parent::__construct($data, ArrayObject::ARRAY_AS_PROPS);
+    }
+
+    /**
+     * Serialize the array to JSON.
+     *
+     * @see https://php.net/jsonserializable.jsonserialize
+     * @return object
+     */
+    #[ReturnTypeWillChange]
+    public function jsonSerialize()
+    {
+        return (object) $this->getArrayCopy();
+    }
+}

+ 156 - 0
api/v1/vendor/mongodb/mongodb/src/Model/BSONIterator.php

@@ -0,0 +1,156 @@
+<?php
+/*
+ * Copyright 2018-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Iterator;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use ReturnTypeWillChange;
+
+use function is_array;
+use function MongoDB\BSON\toPHP;
+use function sprintf;
+use function strlen;
+use function substr;
+use function unpack;
+
+/**
+ * Iterator for BSON documents.
+ */
+class BSONIterator implements Iterator
+{
+    private const BSON_SIZE = 4;
+
+    /** @var string */
+    private $buffer;
+
+    /** @var integer */
+    private $bufferLength;
+
+    /** @var mixed */
+    private $current;
+
+    /** @var integer */
+    private $key = 0;
+
+    /** @var integer */
+    private $position = 0;
+
+    /** @var array */
+    private $options;
+
+    /**
+     * Constructs a BSON Iterator.
+     *
+     * Supported options:
+     *
+     *  * typeMap (array): Type map for BSON deserialization.
+     *
+     * @internal
+     * @see https://php.net/manual/en/function.mongodb.bson-tophp.php
+     * @param string $data    Concatenated, valid, BSON-encoded documents
+     * @param array  $options Iterator options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $data, array $options = [])
+    {
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (! isset($options['typeMap'])) {
+            $options['typeMap'] = [];
+        }
+
+        $this->buffer = $data;
+        $this->bufferLength = strlen($data);
+        $this->options = $options;
+    }
+
+    /**
+     * @see https://php.net/iterator.current
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function current()
+    {
+        return $this->current;
+    }
+
+    /**
+     * @see https://php.net/iterator.key
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function key()
+    {
+        return $this->key;
+    }
+
+    /**
+     * @see https://php.net/iterator.next
+     * @return void
+     */
+    #[ReturnTypeWillChange]
+    public function next()
+    {
+        $this->key++;
+        $this->current = null;
+        $this->advance();
+    }
+
+    /**
+     * @see https://php.net/iterator.rewind
+     * @return void
+     */
+    #[ReturnTypeWillChange]
+    public function rewind()
+    {
+        $this->key = 0;
+        $this->position = 0;
+        $this->current = null;
+        $this->advance();
+    }
+
+    /** @see https://php.net/iterator.valid */
+    #[ReturnTypeWillChange]
+    public function valid(): bool
+    {
+        return $this->current !== null;
+    }
+
+    private function advance(): void
+    {
+        if ($this->position === $this->bufferLength) {
+            return;
+        }
+
+        if ($this->bufferLength - $this->position < self::BSON_SIZE) {
+            throw new UnexpectedValueException(sprintf('Expected at least %d bytes; %d remaining', self::BSON_SIZE, $this->bufferLength - $this->position));
+        }
+
+        [, $documentLength] = unpack('V', substr($this->buffer, $this->position, self::BSON_SIZE));
+
+        if ($this->bufferLength - $this->position < $documentLength) {
+            throw new UnexpectedValueException(sprintf('Expected %d bytes; %d remaining', $documentLength, $this->bufferLength - $this->position));
+        }
+
+        $this->current = toPHP(substr($this->buffer, $this->position, $documentLength), $this->options['typeMap']);
+        $this->position += $documentLength;
+    }
+}

+ 164 - 0
api/v1/vendor/mongodb/mongodb/src/Model/CachingIterator.php

@@ -0,0 +1,164 @@
+<?php
+/*
+ * Copyright 2017-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Countable;
+use Iterator;
+use IteratorIterator;
+use ReturnTypeWillChange;
+use Traversable;
+
+use function count;
+use function current;
+use function next;
+use function reset;
+
+/**
+ * Iterator for wrapping a Traversable and caching its results.
+ *
+ * By caching results, this iterators allows a Traversable to be counted and
+ * rewound multiple times, even if the wrapped object does not natively support
+ * those operations (e.g. MongoDB\Driver\Cursor).
+ *
+ * @internal
+ */
+class CachingIterator implements Countable, Iterator
+{
+    private const FIELD_KEY = 0;
+    private const FIELD_VALUE = 1;
+
+    /** @var array */
+    private $items = [];
+
+    /** @var Iterator */
+    private $iterator;
+
+    /** @var boolean */
+    private $iteratorAdvanced = false;
+
+    /** @var boolean */
+    private $iteratorExhausted = false;
+
+    /**
+     * Initialize the iterator and stores the first item in the cache. This
+     * effectively rewinds the Traversable and the wrapping IteratorIterator.
+     * Additionally, this mimics behavior of the SPL iterators and allows users
+     * to omit an explicit call to rewind() before using the other methods.
+     *
+     * @param Traversable $traversable
+     */
+    public function __construct(Traversable $traversable)
+    {
+        $this->iterator = $traversable instanceof Iterator ? $traversable : new IteratorIterator($traversable);
+
+        $this->iterator->rewind();
+        $this->storeCurrentItem();
+    }
+
+    /** @see https://php.net/countable.count */
+    public function count(): int
+    {
+        $this->exhaustIterator();
+
+        return count($this->items);
+    }
+
+    /**
+     * @see https://php.net/iterator.current
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function current()
+    {
+        $currentItem = current($this->items);
+
+        return $currentItem !== false ? $currentItem[self::FIELD_VALUE] : false;
+    }
+
+    /**
+     * @see https://php.net/iterator.key
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function key()
+    {
+        $currentItem = current($this->items);
+
+        return $currentItem !== false ? $currentItem[self::FIELD_KEY] : null;
+    }
+
+    /** @see https://php.net/iterator.next */
+    public function next(): void
+    {
+        if (! $this->iteratorExhausted) {
+            $this->iteratorAdvanced = true;
+            $this->iterator->next();
+
+            $this->storeCurrentItem();
+
+            $this->iteratorExhausted = ! $this->iterator->valid();
+        }
+
+        next($this->items);
+    }
+
+    /** @see https://php.net/iterator.rewind */
+    public function rewind(): void
+    {
+        /* If the iterator has advanced, exhaust it now so that future iteration
+         * can rely on the cache.
+         */
+        if ($this->iteratorAdvanced) {
+            $this->exhaustIterator();
+        }
+
+        reset($this->items);
+    }
+
+    /** @see https://php.net/iterator.valid */
+    public function valid(): bool
+    {
+        return $this->key() !== null;
+    }
+
+    /**
+     * Ensures that the inner iterator is fully consumed and cached.
+     */
+    private function exhaustIterator(): void
+    {
+        while (! $this->iteratorExhausted) {
+            $this->next();
+        }
+    }
+
+    /**
+     * Stores the current item in the cache.
+     */
+    private function storeCurrentItem(): void
+    {
+        if (! $this->iterator->valid()) {
+            return;
+        }
+
+        // Storing a new item in the internal cache
+        $this->items[] = [
+            self::FIELD_KEY => $this->iterator->key(),
+            self::FIELD_VALUE => $this->iterator->current(),
+        ];
+    }
+}

+ 82 - 0
api/v1/vendor/mongodb/mongodb/src/Model/CallbackIterator.php

@@ -0,0 +1,82 @@
+<?php
+/*
+ * Copyright 2017-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Closure;
+use Iterator;
+use IteratorIterator;
+use ReturnTypeWillChange;
+use Traversable;
+
+/**
+ * Iterator to apply a callback before returning an element
+ *
+ * @internal
+ */
+class CallbackIterator implements Iterator
+{
+    /** @var Closure */
+    private $callback;
+
+    /** @var Iterator */
+    private $iterator;
+
+    public function __construct(Traversable $traversable, Closure $callback)
+    {
+        $this->iterator = $traversable instanceof Iterator ? $traversable : new IteratorIterator($traversable);
+        $this->callback = $callback;
+    }
+
+    /**
+     * @see https://php.net/iterator.current
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function current()
+    {
+        return ($this->callback)($this->iterator->current());
+    }
+
+    /**
+     * @see https://php.net/iterator.key
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function key()
+    {
+        return $this->iterator->key();
+    }
+
+    /** @see https://php.net/iterator.next */
+    public function next(): void
+    {
+        $this->iterator->next();
+    }
+
+    /** @see https://php.net/iterator.rewind */
+    public function rewind(): void
+    {
+        $this->iterator->rewind();
+    }
+
+    /** @see https://php.net/iterator.valid */
+    public function valid(): bool
+    {
+        return $this->iterator->valid();
+    }
+}

+ 304 - 0
api/v1/vendor/mongodb/mongodb/src/Model/ChangeStreamIterator.php

@@ -0,0 +1,304 @@
+<?php
+/*
+ * Copyright 2019-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use IteratorIterator;
+use MongoDB\BSON\Serializable;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Monitoring\CommandFailedEvent;
+use MongoDB\Driver\Monitoring\CommandStartedEvent;
+use MongoDB\Driver\Monitoring\CommandSubscriber;
+use MongoDB\Driver\Monitoring\CommandSucceededEvent;
+use MongoDB\Driver\Server;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\ResumeTokenException;
+use MongoDB\Exception\UnexpectedValueException;
+use ReturnTypeWillChange;
+
+use function assert;
+use function count;
+use function is_array;
+use function is_object;
+use function MongoDB\Driver\Monitoring\addSubscriber;
+use function MongoDB\Driver\Monitoring\removeSubscriber;
+
+/**
+ * ChangeStreamIterator wraps a change stream's tailable cursor.
+ *
+ * This iterator tracks the size of each batch in order to determine when the
+ * postBatchResumeToken is applicable. It also ensures that initial calls to
+ * rewind() do not execute getMore commands.
+ *
+ * @internal
+ */
+class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber
+{
+    /** @var integer */
+    private $batchPosition = 0;
+
+    /** @var integer */
+    private $batchSize;
+
+    /** @var boolean */
+    private $isRewindNop;
+
+    /** @var boolean */
+    private $isValid = false;
+
+    /** @var object|null */
+    private $postBatchResumeToken;
+
+    /** @var array|object|null */
+    private $resumeToken;
+
+    /** @var Server */
+    private $server;
+
+    /**
+     * @internal
+     * @param array|object|null $initialResumeToken
+     */
+    public function __construct(Cursor $cursor, int $firstBatchSize, $initialResumeToken, ?object $postBatchResumeToken)
+    {
+        if (isset($initialResumeToken) && ! is_array($initialResumeToken) && ! is_object($initialResumeToken)) {
+            throw InvalidArgumentException::invalidType('$initialResumeToken', $initialResumeToken, 'array or object');
+        }
+
+        parent::__construct($cursor);
+
+        $this->batchSize = $firstBatchSize;
+        $this->isRewindNop = ($firstBatchSize === 0);
+        $this->postBatchResumeToken = $postBatchResumeToken;
+        $this->resumeToken = $initialResumeToken;
+        $this->server = $cursor->getServer();
+    }
+
+    /** @internal */
+    final public function commandFailed(CommandFailedEvent $event): void
+    {
+    }
+
+    /** @internal */
+    final public function commandStarted(CommandStartedEvent $event): void
+    {
+        if ($event->getCommandName() !== 'getMore') {
+            return;
+        }
+
+        $this->batchPosition = 0;
+        $this->batchSize = 0;
+        $this->postBatchResumeToken = null;
+    }
+
+    /** @internal */
+    final public function commandSucceeded(CommandSucceededEvent $event): void
+    {
+        if ($event->getCommandName() !== 'getMore') {
+            return;
+        }
+
+        $reply = $event->getReply();
+
+        if (! isset($reply->cursor->nextBatch) || ! is_array($reply->cursor->nextBatch)) {
+            throw new UnexpectedValueException('getMore command did not return a "cursor.nextBatch" array');
+        }
+
+        $this->batchSize = count($reply->cursor->nextBatch);
+
+        if (isset($reply->cursor->postBatchResumeToken) && is_object($reply->cursor->postBatchResumeToken)) {
+            $this->postBatchResumeToken = $reply->cursor->postBatchResumeToken;
+        }
+    }
+
+    /**
+     * @see https://php.net/iteratoriterator.current
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function current()
+    {
+        return $this->isValid ? parent::current() : null;
+    }
+
+    /**
+     * Necessary to let psalm know that we're always expecting a cursor as inner
+     * iterator. This could be side-stepped due to the class not being final,
+     * but it's very much an invalid use-case. This method can be dropped in 2.0
+     * once the class is final.
+     */
+    final public function getInnerIterator(): Cursor
+    {
+        $cursor = parent::getInnerIterator();
+        assert($cursor instanceof Cursor);
+
+        return $cursor;
+    }
+
+    /**
+     * Returns the resume token for the iterator's current position.
+     *
+     * Null may be returned if no change documents have been iterated and the
+     * server did not include a postBatchResumeToken in its aggregate or getMore
+     * command response.
+     *
+     * @return array|object|null
+     */
+    public function getResumeToken()
+    {
+        return $this->resumeToken;
+    }
+
+    /**
+     * Returns the server the cursor is running on.
+     */
+    public function getServer(): Server
+    {
+        return $this->server;
+    }
+
+    /**
+     * @see https://php.net/iteratoriterator.key
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function key()
+    {
+        return $this->isValid ? parent::key() : null;
+    }
+
+    /** @see https://php.net/iteratoriterator.rewind */
+    public function next(): void
+    {
+        /* Determine if advancing the iterator will execute a getMore command
+         * (i.e. we are already positioned at the end of the current batch). If
+         * so, rely on the APM callbacks to reset $batchPosition and update
+         * $batchSize. Otherwise, we can forgo APM and manually increment
+         * $batchPosition after calling next(). */
+        $getMore = $this->isAtEndOfBatch();
+
+        if ($getMore) {
+            addSubscriber($this);
+        }
+
+        try {
+            parent::next();
+
+            $this->onIteration(! $getMore);
+        } finally {
+            if ($getMore) {
+                removeSubscriber($this);
+            }
+        }
+    }
+
+    /** @see https://php.net/iteratoriterator.rewind */
+    public function rewind(): void
+    {
+        if ($this->isRewindNop) {
+            return;
+        }
+
+        parent::rewind();
+
+        $this->onIteration(false);
+    }
+
+    /** @see https://php.net/iteratoriterator.valid */
+    public function valid(): bool
+    {
+        return $this->isValid;
+    }
+
+    /**
+     * Extracts the resume token (i.e. "_id" field) from a change document.
+     *
+     * @param array|object $document Change document
+     * @return array|object
+     * @throws InvalidArgumentException
+     * @throws ResumeTokenException if the resume token is not found or invalid
+     */
+    private function extractResumeToken($document)
+    {
+        if (! is_array($document) && ! is_object($document)) {
+            throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
+        }
+
+        if ($document instanceof Serializable) {
+            return $this->extractResumeToken($document->bsonSerialize());
+        }
+
+        $resumeToken = is_array($document)
+            ? ($document['_id'] ?? null)
+            : ($document->_id ?? null);
+
+        if (! isset($resumeToken)) {
+            $this->isValid = false;
+
+            throw ResumeTokenException::notFound();
+        }
+
+        if (! is_array($resumeToken) && ! is_object($resumeToken)) {
+            $this->isValid = false;
+
+            throw ResumeTokenException::invalidType($resumeToken);
+        }
+
+        return $resumeToken;
+    }
+
+    /**
+     * Return whether the iterator is positioned at the end of the batch.
+     */
+    private function isAtEndOfBatch(): bool
+    {
+        return $this->batchPosition + 1 >= $this->batchSize;
+    }
+
+    /**
+     * Perform housekeeping after an iteration event.
+     *
+     * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#updating-the-cached-resume-token
+     */
+    private function onIteration(bool $incrementBatchPosition): void
+    {
+        $this->isValid = parent::valid();
+
+        /* Disable rewind()'s NOP behavior once we advance to a valid position.
+         * This will allow the driver to throw a LogicException if rewind() is
+         * called after the cursor has advanced past its first element. */
+        if ($this->isRewindNop && $this->isValid) {
+            $this->isRewindNop = false;
+        }
+
+        if ($incrementBatchPosition && $this->isValid) {
+            $this->batchPosition++;
+        }
+
+        /* If the iterator is positioned at the end of the batch, apply the
+         * postBatchResumeToken if it's available. This handles both the case
+         * where the current batch is empty (since onIteration() will be called
+         * after a successful getMore) and when the iterator has advanced to the
+         * last document in its current batch. Otherwise, extract a resume token
+         * from the current document if possible. */
+        if ($this->isAtEndOfBatch() && $this->postBatchResumeToken !== null) {
+            $this->resumeToken = $this->postBatchResumeToken;
+        } elseif ($this->isValid) {
+            $this->resumeToken = $this->extractResumeToken($this->current());
+        }
+    }
+}

+ 200 - 0
api/v1/vendor/mongodb/mongodb/src/Model/CollectionInfo.php

@@ -0,0 +1,200 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use ArrayAccess;
+use MongoDB\Exception\BadMethodCallException;
+use ReturnTypeWillChange;
+
+use function array_key_exists;
+
+/**
+ * Collection information model class.
+ *
+ * This class models the collection information returned by the listCollections
+ * command or, for legacy servers, queries on the "system.namespaces"
+ * collection. It provides methods to access options for the collection.
+ *
+ * @see \MongoDB\Database::listCollections()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
+ */
+class CollectionInfo implements ArrayAccess
+{
+    /** @var array */
+    private $info;
+
+    /** @param array $info Collection info */
+    public function __construct(array $info)
+    {
+        $this->info = $info;
+    }
+
+    /**
+     * Return the collection info as an array.
+     *
+     * @see https://php.net/oop5.magic#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return $this->info;
+    }
+
+    /**
+     * Return the maximum number of documents to keep in the capped collection.
+     *
+     * @deprecated 1.0 Deprecated in favor of using getOptions
+     *
+     * @return integer|null
+     */
+    public function getCappedMax()
+    {
+        /* The MongoDB server might return this number as an integer or float */
+        return isset($this->info['options']['max']) ? (integer) $this->info['options']['max'] : null;
+    }
+
+    /**
+     * Return the maximum size (in bytes) of the capped collection.
+     *
+     * @deprecated 1.0 Deprecated in favor of using getOptions
+     *
+     * @return integer|null
+     */
+    public function getCappedSize()
+    {
+        /* The MongoDB server might return this number as an integer or float */
+        return isset($this->info['options']['size']) ? (integer) $this->info['options']['size'] : null;
+    }
+
+    /**
+     * Return information about the _id index for the collection.
+     */
+    public function getIdIndex(): array
+    {
+        return (array) ($this->info['idIndex'] ?? []);
+    }
+
+    /**
+     * Return the "info" property of the server response.
+     *
+     * @see https://mongodb.com/docs/manual/reference/command/listCollections/#output
+     */
+    public function getInfo(): array
+    {
+        return (array) ($this->info['info'] ?? []);
+    }
+
+    /**
+     * Return the collection name.
+     *
+     * @see https://mongodb.com/docs/manual/reference/command/listCollections/#output
+     * @return string
+     */
+    public function getName()
+    {
+        return (string) $this->info['name'];
+    }
+
+    /**
+     * Return the collection options.
+     *
+     * @see https://mongodb.com/docs/manual/reference/command/listCollections/#output
+     * @return array
+     */
+    public function getOptions()
+    {
+        return (array) ($this->info['options'] ?? []);
+    }
+
+    /**
+     * Return the collection type.
+     *
+     * @see https://mongodb.com/docs/manual/reference/command/listCollections/#output
+     */
+    public function getType(): string
+    {
+        return (string) $this->info['type'];
+    }
+
+    /**
+     * Return whether the collection is a capped collection.
+     *
+     * @deprecated 1.0 Deprecated in favor of using getOptions
+     *
+     * @return boolean
+     */
+    public function isCapped()
+    {
+        return ! empty($this->info['options']['capped']);
+    }
+
+    /**
+     * Check whether a field exists in the collection information.
+     *
+     * @see https://php.net/arrayaccess.offsetexists
+     * @param mixed $key
+     * @return boolean
+     */
+    #[ReturnTypeWillChange]
+    public function offsetExists($key)
+    {
+        return array_key_exists($key, $this->info);
+    }
+
+    /**
+     * Return the field's value from the collection information.
+     *
+     * @see https://php.net/arrayaccess.offsetget
+     * @param mixed $key
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function offsetGet($key)
+    {
+        return $this->info[$key];
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see https://php.net/arrayaccess.offsetset
+     * @param mixed $key
+     * @param mixed $value
+     * @throws BadMethodCallException
+     * @return void
+     */
+    #[ReturnTypeWillChange]
+    public function offsetSet($key, $value)
+    {
+        throw BadMethodCallException::classIsImmutable(self::class);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see https://php.net/arrayaccess.offsetunset
+     * @param mixed $key
+     * @throws BadMethodCallException
+     * @return void
+     */
+    #[ReturnTypeWillChange]
+    public function offsetUnset($key)
+    {
+        throw BadMethodCallException::classIsImmutable(self::class);
+    }
+}

+ 62 - 0
api/v1/vendor/mongodb/mongodb/src/Model/CollectionInfoCommandIterator.php

@@ -0,0 +1,62 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use IteratorIterator;
+use Traversable;
+
+/**
+ * CollectionInfoIterator for listCollections command results.
+ *
+ * This iterator may be used to wrap a Cursor returned by the listCollections
+ * command.
+ *
+ * @internal
+ * @see \MongoDB\Database::listCollections()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst
+ * @see https://mongodb.com/docs/manual/reference/command/listCollections/
+ */
+class CollectionInfoCommandIterator extends IteratorIterator implements CollectionInfoIterator
+{
+    /** @var string|null */
+    private $databaseName;
+
+    public function __construct(Traversable $iterator, ?string $databaseName = null)
+    {
+        parent::__construct($iterator);
+
+        $this->databaseName = $databaseName;
+    }
+
+    /**
+     * Return the current element as a CollectionInfo instance.
+     *
+     * @see CollectionInfoIterator::current()
+     * @see https://php.net/iterator.current
+     */
+    public function current(): CollectionInfo
+    {
+        $info = parent::current();
+
+        if ($this->databaseName !== null && isset($info['idIndex']) && ! isset($info['idIndex']['ns'])) {
+            $info['idIndex']['ns'] = $this->databaseName . '.' . $info['name'];
+        }
+
+        return new CollectionInfo($info);
+    }
+}

+ 39 - 0
api/v1/vendor/mongodb/mongodb/src/Model/CollectionInfoIterator.php

@@ -0,0 +1,39 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Iterator;
+use ReturnTypeWillChange;
+
+/**
+ * CollectionInfoIterator interface.
+ *
+ * This iterator is used for enumerating collections in a database.
+ *
+ * @see \MongoDB\Database::listCollections()
+ */
+interface CollectionInfoIterator extends Iterator
+{
+    /**
+     * Return the current element as a CollectionInfo instance.
+     *
+     * @return CollectionInfo
+     */
+    #[ReturnTypeWillChange]
+    public function current();
+}

+ 142 - 0
api/v1/vendor/mongodb/mongodb/src/Model/DatabaseInfo.php

@@ -0,0 +1,142 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use ArrayAccess;
+use MongoDB\Exception\BadMethodCallException;
+use ReturnTypeWillChange;
+
+use function array_key_exists;
+
+/**
+ * Database information model class.
+ *
+ * This class models the database information returned by the listDatabases
+ * command. It provides methods to access common database properties.
+ *
+ * @see \MongoDB\Client::listDatabases()
+ * @see https://mongodb.com/docs/manual/reference/command/listDatabases/
+ */
+class DatabaseInfo implements ArrayAccess
+{
+    /** @var array */
+    private $info;
+
+    /** @param array $info Database info */
+    public function __construct(array $info)
+    {
+        $this->info = $info;
+    }
+
+    /**
+     * Return the database info as an array.
+     *
+     * @see https://php.net/oop5.magic#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return $this->info;
+    }
+
+    /**
+     * Return the database name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return (string) $this->info['name'];
+    }
+
+    /**
+     * Return the databases size on disk (in bytes).
+     *
+     * @return integer
+     */
+    public function getSizeOnDisk()
+    {
+        /* The MongoDB server might return this number as an integer or float */
+        return (integer) $this->info['sizeOnDisk'];
+    }
+
+    /**
+     * Return whether the database is empty.
+     *
+     * @return boolean
+     */
+    public function isEmpty()
+    {
+        return (boolean) $this->info['empty'];
+    }
+
+    /**
+     * Check whether a field exists in the database information.
+     *
+     * @see https://php.net/arrayaccess.offsetexists
+     * @param mixed $key
+     * @return boolean
+     */
+    #[ReturnTypeWillChange]
+    public function offsetExists($key)
+    {
+        return array_key_exists($key, $this->info);
+    }
+
+    /**
+     * Return the field's value from the database information.
+     *
+     * @see https://php.net/arrayaccess.offsetget
+     * @param mixed $key
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function offsetGet($key)
+    {
+        return $this->info[$key];
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see https://php.net/arrayaccess.offsetset
+     * @param mixed $key
+     * @param mixed $value
+     * @throws BadMethodCallException
+     * @return void
+     */
+    #[ReturnTypeWillChange]
+    public function offsetSet($key, $value)
+    {
+        throw BadMethodCallException::classIsImmutable(self::class);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see https://php.net/arrayaccess.offsetunset
+     * @param mixed $key
+     * @throws BadMethodCallException
+     * @return void
+     */
+    #[ReturnTypeWillChange]
+    public function offsetUnset($key)
+    {
+        throw BadMethodCallException::classIsImmutable(self::class);
+    }
+}

+ 39 - 0
api/v1/vendor/mongodb/mongodb/src/Model/DatabaseInfoIterator.php

@@ -0,0 +1,39 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Iterator;
+use ReturnTypeWillChange;
+
+/**
+ * DatabaseInfoIterator interface.
+ *
+ * This iterator is used for enumerating databases on a server.
+ *
+ * @see \MongoDB\Client::listDatabases()
+ */
+interface DatabaseInfoIterator extends Iterator
+{
+    /**
+     * Return the current element as a DatabaseInfo instance.
+     *
+     * @return DatabaseInfo
+     */
+    #[ReturnTypeWillChange]
+    public function current();
+}

+ 95 - 0
api/v1/vendor/mongodb/mongodb/src/Model/DatabaseInfoLegacyIterator.php

@@ -0,0 +1,95 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use function current;
+use function key;
+use function next;
+use function reset;
+
+/**
+ * DatabaseInfoIterator for inline listDatabases command results.
+ *
+ * This iterator may be used to wrap the array returned within the listDatabases
+ * command's single-document result.
+ *
+ * @internal
+ * @see \MongoDB\Client::listDatabases()
+ * @see https://mongodb.com/docs/manual/reference/command/listDatabases/
+ */
+class DatabaseInfoLegacyIterator implements DatabaseInfoIterator
+{
+    /** @var array */
+    private $databases;
+
+    public function __construct(array $databases)
+    {
+        $this->databases = $databases;
+    }
+
+    /**
+     * Return the current element as a DatabaseInfo instance.
+     *
+     * @see DatabaseInfoIterator::current()
+     * @see https://php.net/iterator.current
+     */
+    public function current(): DatabaseInfo
+    {
+        return new DatabaseInfo(current($this->databases));
+    }
+
+    /**
+     * Return the key of the current element.
+     *
+     * @see https://php.net/iterator.key
+     */
+    public function key(): int
+    {
+        return key($this->databases);
+    }
+
+    /**
+     * Move forward to next element.
+     *
+     * @see https://php.net/iterator.next
+     */
+    public function next(): void
+    {
+        next($this->databases);
+    }
+
+    /**
+     * Rewind the Iterator to the first element.
+     *
+     * @see https://php.net/iterator.rewind
+     */
+    public function rewind(): void
+    {
+        reset($this->databases);
+    }
+
+    /**
+     * Checks if current position is valid.
+     *
+     * @see https://php.net/iterator.valid
+     */
+    public function valid(): bool
+    {
+        return key($this->databases) !== null;
+    }
+}

+ 241 - 0
api/v1/vendor/mongodb/mongodb/src/Model/IndexInfo.php

@@ -0,0 +1,241 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use ArrayAccess;
+use MongoDB\Exception\BadMethodCallException;
+use ReturnTypeWillChange;
+
+use function array_key_exists;
+use function array_search;
+use function trigger_error;
+
+use const E_USER_DEPRECATED;
+
+/**
+ * Index information model class.
+ *
+ * This class models the index information returned by the listIndexes command
+ * or, for legacy servers, queries on the "system.indexes" collection. It
+ * provides methods to access common index options, and allows access to other
+ * options through the ArrayAccess interface (write methods are not supported).
+ * For information on keys and index options, see the referenced
+ * db.collection.createIndex() documentation.
+ *
+ * @see \MongoDB\Collection::listIndexes()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
+ * @see https://mongodb.com/docs/manual/reference/method/db.collection.createIndex/
+ */
+class IndexInfo implements ArrayAccess
+{
+    /** @var array */
+    private $info;
+
+    /** @param array $info Index info */
+    public function __construct(array $info)
+    {
+        $this->info = $info;
+    }
+
+    /**
+     * Return the collection info as an array.
+     *
+     * @see https://php.net/oop5.magic#language.oop5.magic.debuginfo
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return $this->info;
+    }
+
+    /**
+     * Return the index name to allow casting IndexInfo to string.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->getName();
+    }
+
+    /**
+     * Return the index key.
+     *
+     * @return array
+     */
+    public function getKey()
+    {
+        return (array) $this->info['key'];
+    }
+
+    /**
+     * Return the index name.
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return (string) $this->info['name'];
+    }
+
+    /**
+     * Return the index namespace (e.g. "db.collection").
+     *
+     * @return string
+     */
+    public function getNamespace()
+    {
+        return (string) $this->info['ns'];
+    }
+
+    /**
+     * Return the index version.
+     *
+     * @return integer
+     */
+    public function getVersion()
+    {
+        return (integer) $this->info['v'];
+    }
+
+    /**
+     * Return whether or not this index is of type 2dsphere.
+     *
+     * @return boolean
+     */
+    public function is2dSphere()
+    {
+        return array_search('2dsphere', $this->getKey(), true) !== false;
+    }
+
+    /**
+     * Return whether or not this index is of type geoHaystack.
+     *
+     * @return boolean
+     * @deprecated Since 1.16: MongoDB 5.0 removes support for geoHaystack indexes.
+     */
+    public function isGeoHaystack()
+    {
+        trigger_error('MongoDB 5.0 removes support for "geoHaystack" indexes, the method "IndexInfo::isGeoHaystack()" will be removed in a future release', E_USER_DEPRECATED);
+
+        return array_search('geoHaystack', $this->getKey(), true) !== false;
+    }
+
+    /**
+     * Return whether this is a sparse index.
+     *
+     * @see https://mongodb.com/docs/manual/core/index-sparse/
+     * @return boolean
+     */
+    public function isSparse()
+    {
+        return ! empty($this->info['sparse']);
+    }
+
+    /**
+     * Return whether or not this index is of type text.
+     *
+     * @return boolean
+     */
+    public function isText()
+    {
+        return array_search('text', $this->getKey(), true) !== false;
+    }
+
+    /**
+     * Return whether this is a TTL index.
+     *
+     * @see https://mongodb.com/docs/manual/core/index-ttl/
+     * @return boolean
+     */
+    public function isTtl()
+    {
+        return array_key_exists('expireAfterSeconds', $this->info);
+    }
+
+    /**
+     * Return whether this is a unique index.
+     *
+     * @see https://mongodb.com/docs/manual/core/index-unique/
+     * @return boolean
+     */
+    public function isUnique()
+    {
+        return ! empty($this->info['unique']);
+    }
+
+    /**
+     * Check whether a field exists in the index information.
+     *
+     * @see https://php.net/arrayaccess.offsetexists
+     * @param mixed $key
+     * @return boolean
+     */
+    #[ReturnTypeWillChange]
+    public function offsetExists($key)
+    {
+        return array_key_exists($key, $this->info);
+    }
+
+    /**
+     * Return the field's value from the index information.
+     *
+     * This method satisfies the Enumerating Indexes specification's requirement
+     * that index fields be made accessible under their original names. It may
+     * also be used to access fields that do not have a helper method.
+     *
+     * @see https://php.net/arrayaccess.offsetget
+     * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst#getting-full-index-information
+     * @param mixed $key
+     * @return mixed
+     */
+    #[ReturnTypeWillChange]
+    public function offsetGet($key)
+    {
+        return $this->info[$key];
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see https://php.net/arrayaccess.offsetset
+     * @param mixed $key
+     * @param mixed $value
+     * @throws BadMethodCallException
+     * @return void
+     */
+    #[ReturnTypeWillChange]
+    public function offsetSet($key, $value)
+    {
+        throw BadMethodCallException::classIsImmutable(self::class);
+    }
+
+    /**
+     * Not supported.
+     *
+     * @see https://php.net/arrayaccess.offsetunset
+     * @param mixed $key
+     * @throws BadMethodCallException
+     * @return void
+     */
+    #[ReturnTypeWillChange]
+    public function offsetUnset($key)
+    {
+        throw BadMethodCallException::classIsImmutable(self::class);
+    }
+}

+ 39 - 0
api/v1/vendor/mongodb/mongodb/src/Model/IndexInfoIterator.php

@@ -0,0 +1,39 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use Iterator;
+use ReturnTypeWillChange;
+
+/**
+ * IndexInfoIterator interface.
+ *
+ * This iterator is used for enumerating indexes in a collection.
+ *
+ * @see \MongoDB\Collection::listIndexes()
+ */
+interface IndexInfoIterator extends Iterator
+{
+    /**
+     * Return the current element as a IndexInfo instance.
+     *
+     * @return IndexInfo
+     */
+    #[ReturnTypeWillChange]
+    public function current();
+}

+ 66 - 0
api/v1/vendor/mongodb/mongodb/src/Model/IndexInfoIteratorIterator.php

@@ -0,0 +1,66 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use IteratorIterator;
+use Traversable;
+
+use function array_key_exists;
+
+/**
+ * IndexInfoIterator for both listIndexes command and legacy query results.
+ *
+ * This common iterator may be used to wrap a Cursor returned by both the
+ * listIndexes command and, for legacy servers, queries on the "system.indexes"
+ * collection.
+ *
+ * @internal
+ * @see \MongoDB\Collection::listIndexes()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
+ * @see https://mongodb.com/docs/manual/reference/command/listIndexes/
+ * @see https://mongodb.com/docs/manual/reference/system-collections/
+ */
+class IndexInfoIteratorIterator extends IteratorIterator implements IndexInfoIterator
+{
+    /** @var string|null $ns */
+    private $ns;
+
+    public function __construct(Traversable $iterator, ?string $ns = null)
+    {
+        parent::__construct($iterator);
+
+        $this->ns = $ns;
+    }
+
+    /**
+     * Return the current element as an IndexInfo instance.
+     *
+     * @see IndexInfoIterator::current()
+     * @see https://php.net/iterator.current
+     */
+    public function current(): IndexInfo
+    {
+        $info = parent::current();
+
+        if (! array_key_exists('ns', $info) && $this->ns !== null) {
+            $info['ns'] = $this->ns;
+        }
+
+        return new IndexInfo($info);
+    }
+}

+ 115 - 0
api/v1/vendor/mongodb/mongodb/src/Model/IndexInput.php

@@ -0,0 +1,115 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Model;
+
+use MongoDB\BSON\Serializable;
+use MongoDB\Exception\InvalidArgumentException;
+
+use function is_array;
+use function is_float;
+use function is_int;
+use function is_object;
+use function is_string;
+use function MongoDB\document_to_array;
+use function sprintf;
+
+/**
+ * Index input model class.
+ *
+ * This class is used to validate user input for index creation.
+ *
+ * @internal
+ * @see \MongoDB\Collection::createIndexes()
+ * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst
+ * @see https://mongodb.com/docs/manual/reference/method/db.collection.createIndex/
+ */
+class IndexInput implements Serializable
+{
+    /** @var array */
+    private $index;
+
+    /**
+     * @param array $index Index specification
+     * @throws InvalidArgumentException
+     */
+    public function __construct(array $index)
+    {
+        if (! isset($index['key'])) {
+            throw new InvalidArgumentException('Required "key" document is missing from index specification');
+        }
+
+        if (! is_array($index['key']) && ! is_object($index['key'])) {
+            throw InvalidArgumentException::invalidType('"key" option', $index['key'], 'array or object');
+        }
+
+        foreach ($index['key'] as $fieldName => $order) {
+            if (! is_int($order) && ! is_float($order) && ! is_string($order)) {
+                throw InvalidArgumentException::invalidType(sprintf('order value for "%s" field within "key" option', $fieldName), $order, 'numeric or string');
+            }
+        }
+
+        if (! isset($index['name'])) {
+            $index['name'] = $this->generateIndexName($index['key']);
+        }
+
+        if (! is_string($index['name'])) {
+            throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string');
+        }
+
+        $this->index = $index;
+    }
+
+    /**
+     * Return the index name.
+     */
+    public function __toString(): string
+    {
+        return $this->index['name'];
+    }
+
+    /**
+     * Serialize the index information to BSON for index creation.
+     *
+     * @see \MongoDB\Collection::createIndexes()
+     * @see https://php.net/mongodb-bson-serializable.bsonserialize
+     */
+    public function bsonSerialize(): array
+    {
+        return $this->index;
+    }
+
+    /**
+     * Generate an index name from a key specification.
+     *
+     * @param array|object $document Document containing fields mapped to values,
+     *                               which denote order or an index type
+     * @throws InvalidArgumentException if $document is not an array or object
+     */
+    private function generateIndexName($document): string
+    {
+        $document = document_to_array($document);
+
+        $name = '';
+
+        foreach ($document as $field => $type) {
+            $name .= ($name !== '' ? '_' : '') . $field . '_' . $type;
+        }
+
+        return $name;
+    }
+}

+ 392 - 0
api/v1/vendor/mongodb/mongodb/src/Operation/Aggregate.php

@@ -0,0 +1,392 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use ArrayIterator;
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+use stdClass;
+
+use function current;
+use function is_array;
+use function is_bool;
+use function is_integer;
+use function is_object;
+use function is_string;
+use function MongoDB\create_field_path_type_map;
+use function MongoDB\is_last_pipeline_operator_write;
+use function MongoDB\is_pipeline;
+
+/**
+ * Operation for the aggregate command.
+ *
+ * @see \MongoDB\Collection::aggregate()
+ * @see https://mongodb.com/docs/manual/reference/command/aggregate/
+ */
+class Aggregate implements Executable, Explainable
+{
+    /** @var string */
+    private $databaseName;
+
+    /** @var string|null */
+    private $collectionName;
+
+    /** @var array */
+    private $pipeline;
+
+    /** @var array */
+    private $options;
+
+    /** @var bool */
+    private $isExplain;
+
+    /** @var bool */
+    private $isWrite;
+
+    /**
+     * Constructs an aggregate command.
+     *
+     * Supported options:
+     *
+     *  * allowDiskUse (boolean): Enables writing to temporary files. When set
+     *    to true, aggregation stages can write data to the _tmp sub-directory
+     *    in the dbPath directory.
+     *
+     *  * batchSize (integer): The number of documents to return per batch.
+     *
+     *  * bypassDocumentValidation (boolean): If true, allows the write to
+     *    circumvent document level validation. This only applies when an $out
+     *    or $merge stage is specified.
+     *
+     *  * collation (document): Collation specification.
+     *
+     *  * comment (mixed): BSON value to attach as a comment to this command.
+     *
+     *    Only string values are supported for server versions < 4.4.
+     *
+     *  * explain (boolean): Specifies whether or not to return the information
+     *    on the processing of the pipeline.
+     *
+     *  * hint (string|document): The index to use. Specify either the index
+     *    name as a string or the index key pattern as a document. If specified,
+     *    then the query system will only consider plans using the hinted index.
+     *
+     *  * let (document): Map of parameter names and values. Values must be
+     *    constant or closed expressions that do not reference document fields.
+     *    Parameters can then be accessed as variables in an aggregate
+     *    expression context (e.g. "$$var").
+     *
+     *    This is not supported for server versions < 5.0 and will result in an
+     *    exception at execution time if used.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *    This option is ignored if an $out or $merge stage is specified.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *  * typeMap (array): Type map for BSON deserialization. This will be
+     *    applied to the returned Cursor (it is not sent to the server).
+     *
+     *  * useCursor (boolean): Indicates whether the command will request that
+     *    the server provide results using a cursor. The default is true.
+     *
+     *    This option allows users to turn off cursors if necessary to aid in
+     *    mongod/mongos upgrades.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern. This only
+     *    applies when an $out or $merge stage is specified.
+     *
+     * Note: Collection-agnostic commands (e.g. $currentOp) may be executed by
+     * specifying null for the collection name.
+     *
+     * @param string      $databaseName   Database name
+     * @param string|null $collectionName Collection name
+     * @param array       $pipeline       Aggregation pipeline
+     * @param array       $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, ?string $collectionName, array $pipeline, array $options = [])
+    {
+        if (! is_pipeline($pipeline, true /* allowEmpty */)) {
+            throw new InvalidArgumentException('$pipeline is not a valid aggregation pipeline');
+        }
+
+        $options += ['useCursor' => true];
+
+        if (isset($options['allowDiskUse']) && ! is_bool($options['allowDiskUse'])) {
+            throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean');
+        }
+
+        if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) {
+            throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer');
+        }
+
+        if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
+            throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['explain']) && ! is_bool($options['explain'])) {
+            throw InvalidArgumentException::invalidType('"explain" option', $options['explain'], 'boolean');
+        }
+
+        if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
+            throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
+        }
+
+        if (isset($options['let']) && ! is_array($options['let']) && ! is_object($options['let'])) {
+            throw InvalidArgumentException::invalidType('"let" option', $options['let'], ['array', 'object']);
+        }
+
+        if (isset($options['maxAwaitTimeMS']) && ! is_integer($options['maxAwaitTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $options['maxAwaitTimeMS'], 'integer');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (! is_bool($options['useCursor'])) {
+            throw InvalidArgumentException::invalidType('"useCursor" option', $options['useCursor'], 'boolean');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
+        }
+
+        if (isset($options['batchSize']) && ! $options['useCursor']) {
+            throw new InvalidArgumentException('"batchSize" option should not be used if "useCursor" is false');
+        }
+
+        if (isset($options['bypassDocumentValidation']) && ! $options['bypassDocumentValidation']) {
+            unset($options['bypassDocumentValidation']);
+        }
+
+        if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
+            unset($options['readConcern']);
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        $this->isExplain = ! empty($options['explain']);
+        $this->isWrite = is_last_pipeline_operator_write($pipeline) && ! $this->isExplain;
+
+        // Explain does not use a cursor
+        if ($this->isExplain) {
+            $options['useCursor'] = false;
+            unset($options['batchSize']);
+        }
+
+        /* Ignore batchSize for writes, since no documents are returned and a
+         * batchSize of zero could prevent the pipeline from executing. */
+        if ($this->isWrite) {
+            unset($options['batchSize']);
+        }
+
+        $this->databaseName = $databaseName;
+        $this->collectionName = $collectionName;
+        $this->pipeline = $pipeline;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @return ArrayIterator|Cursor
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if read concern or write concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
+        if ($inTransaction) {
+            if (isset($this->options['readConcern'])) {
+                throw UnsupportedException::readConcernNotSupportedInTransaction();
+            }
+
+            if (isset($this->options['writeConcern'])) {
+                throw UnsupportedException::writeConcernNotSupportedInTransaction();
+            }
+        }
+
+        $command = new Command(
+            $this->createCommandDocument(),
+            $this->createCommandOptions()
+        );
+
+        $cursor = $this->executeCommand($server, $command);
+
+        if ($this->options['useCursor'] || $this->isExplain) {
+            if (isset($this->options['typeMap'])) {
+                $cursor->setTypeMap($this->options['typeMap']);
+            }
+
+            return $cursor;
+        }
+
+        if (isset($this->options['typeMap'])) {
+            $cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'result.$'));
+        }
+
+        $result = current($cursor->toArray());
+
+        if (! is_object($result) || ! isset($result->result) || ! is_array($result->result)) {
+            throw new UnexpectedValueException('aggregate command did not return a "result" array');
+        }
+
+        return new ArrayIterator($result->result);
+    }
+
+    /**
+     * Returns the command document for this operation.
+     *
+     * @see Explainable::getCommandDocument()
+     * @return array
+     */
+    public function getCommandDocument()
+    {
+        $cmd = $this->createCommandDocument();
+
+        // Read concern can change the query plan
+        if (isset($this->options['readConcern'])) {
+            $cmd['readConcern'] = $this->options['readConcern'];
+        }
+
+        return $cmd;
+    }
+
+    /**
+     * Create the aggregate command document.
+     */
+    private function createCommandDocument(): array
+    {
+        $cmd = [
+            'aggregate' => $this->collectionName ?? 1,
+            'pipeline' => $this->pipeline,
+        ];
+
+        foreach (['allowDiskUse', 'bypassDocumentValidation', 'comment', 'explain', 'maxTimeMS'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        foreach (['collation', 'let'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = (object) $this->options[$option];
+            }
+        }
+
+        if (isset($this->options['hint'])) {
+            $cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
+        }
+
+        if ($this->options['useCursor']) {
+            $cmd['cursor'] = isset($this->options["batchSize"])
+                ? ['batchSize' => $this->options["batchSize"]]
+                : new stdClass();
+        }
+
+        return $cmd;
+    }
+
+    private function createCommandOptions(): array
+    {
+        $cmdOptions = [];
+
+        if (isset($this->options['maxAwaitTimeMS'])) {
+            $cmdOptions['maxAwaitTimeMS'] = $this->options['maxAwaitTimeMS'];
+        }
+
+        return $cmdOptions;
+    }
+
+    /**
+     * Execute the aggregate command using the appropriate Server method.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-server.executecommand.php
+     * @see https://php.net/manual/en/mongodb-driver-server.executereadcommand.php
+     * @see https://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php
+     */
+    private function executeCommand(Server $server, Command $command): Cursor
+    {
+        $options = [];
+
+        foreach (['readConcern', 'readPreference', 'session'] as $option) {
+            if (isset($this->options[$option])) {
+                $options[$option] = $this->options[$option];
+            }
+        }
+
+        if ($this->isWrite && isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        if (! $this->isWrite) {
+            return $server->executeReadCommand($this->databaseName, $command, $options);
+        }
+
+        /* Server::executeReadWriteCommand() does not support a "readPreference"
+         * option, so fall back to executeCommand(). This means that libmongoc
+         * will not apply any client-level options (e.g. writeConcern), but that
+         * should not be an issue as PHPLIB handles inheritance on its own. */
+        if (isset($options['readPreference'])) {
+            return $server->executeCommand($this->databaseName, $command, $options);
+        }
+
+        return $server->executeReadWriteCommand($this->databaseName, $command, $options);
+    }
+}

+ 396 - 0
api/v1/vendor/mongodb/mongodb/src/Operation/BulkWrite.php

@@ -0,0 +1,396 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\BulkWriteResult;
+use MongoDB\Driver\BulkWrite as Bulk;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+
+use function array_is_list;
+use function array_key_exists;
+use function count;
+use function current;
+use function is_array;
+use function is_bool;
+use function is_object;
+use function key;
+use function MongoDB\is_first_key_operator;
+use function MongoDB\is_pipeline;
+use function sprintf;
+
+/**
+ * Operation for executing multiple write operations.
+ *
+ * @see \MongoDB\Collection::bulkWrite()
+ */
+class BulkWrite implements Executable
+{
+    public const DELETE_MANY = 'deleteMany';
+    public const DELETE_ONE  = 'deleteOne';
+    public const INSERT_ONE  = 'insertOne';
+    public const REPLACE_ONE = 'replaceOne';
+    public const UPDATE_MANY = 'updateMany';
+    public const UPDATE_ONE  = 'updateOne';
+
+    /** @var string */
+    private $databaseName;
+
+    /** @var string */
+    private $collectionName;
+
+    /** @var array[] */
+    private $operations;
+
+    /** @var array */
+    private $options;
+
+    /**
+     * Constructs a bulk write operation.
+     *
+     * Example array structure for all supported operation types:
+     *
+     *  [
+     *    [ 'deleteMany' => [ $filter, $options ] ],
+     *    [ 'deleteOne'  => [ $filter, $options ] ],
+     *    [ 'insertOne'  => [ $document ] ],
+     *    [ 'replaceOne' => [ $filter, $replacement, $options ] ],
+     *    [ 'updateMany' => [ $filter, $update, $options ] ],
+     *    [ 'updateOne'  => [ $filter, $update, $options ] ],
+     *  ]
+     *
+     * Arguments correspond to the respective Operation classes; however, the
+     * writeConcern option is specified for the top-level bulk write operation
+     * instead of each individual operation.
+     *
+     * Supported options for deleteMany and deleteOne operations:
+     *
+     *  * collation (document): Collation specification.
+     *
+     * Supported options for replaceOne, updateMany, and updateOne operations:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *  * upsert (boolean): When true, a new document is created if no document
+     *    matches the query. The default is false.
+     *
+     * Supported options for updateMany and updateOne operations:
+     *
+     *  * arrayFilters (document array): A set of filters specifying to which
+     *    array elements an update should apply.
+     *
+     * Supported options for the bulk write operation:
+     *
+     *  * bypassDocumentValidation (boolean): If true, allows the write to
+     *    circumvent document level validation. The default is false.
+     *
+     *  * comment (mixed): BSON value to attach as a comment to this command(s)
+     *    associated with this bulk write.
+     *
+     *    This is not supported for servers versions < 4.4.
+     *
+     *  * ordered (boolean): If true, when an insert fails, return without
+     *    performing the remaining writes. If false, when a write fails,
+     *    continue with the remaining writes, if any. The default is true.
+     *
+     *  * let (document): Map of parameter names and values. Values must be
+     *    constant or closed expressions that do not reference document fields.
+     *    Parameters can then be accessed as variables in an aggregate
+     *    expression context (e.g. "$$var").
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @param string  $databaseName   Database name
+     * @param string  $collectionName Collection name
+     * @param array[] $operations     List of write operations
+     * @param array   $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, string $collectionName, array $operations, array $options = [])
+    {
+        if (empty($operations)) {
+            throw new InvalidArgumentException('$operations is empty');
+        }
+
+        if (! array_is_list($operations)) {
+            throw new InvalidArgumentException('$operations is not a list');
+        }
+
+        foreach ($operations as $i => $operation) {
+            if (! is_array($operation)) {
+                throw InvalidArgumentException::invalidType(sprintf('$operations[%d]', $i), $operation, 'array');
+            }
+
+            if (count($operation) !== 1) {
+                throw new InvalidArgumentException(sprintf('Expected one element in $operation[%d], actually: %d', $i, count($operation)));
+            }
+
+            $type = key($operation);
+            $args = current($operation);
+
+            if (! isset($args[0]) && ! array_key_exists(0, $args)) {
+                throw new InvalidArgumentException(sprintf('Missing first argument for $operations[%d]["%s"]', $i, $type));
+            }
+
+            if (! is_array($args[0]) && ! is_object($args[0])) {
+                throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][0]', $i, $type), $args[0], 'array or object');
+            }
+
+            switch ($type) {
+                case self::INSERT_ONE:
+                    break;
+
+                case self::DELETE_MANY:
+                case self::DELETE_ONE:
+                    if (! isset($args[1])) {
+                        $args[1] = [];
+                    }
+
+                    if (! is_array($args[1])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array');
+                    }
+
+                    $args[1]['limit'] = ($type === self::DELETE_ONE ? 1 : 0);
+
+                    if (isset($args[1]['collation']) && ! is_array($args[1]['collation']) && ! is_object($args[1]['collation'])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]["collation"]', $i, $type), $args[1]['collation'], 'array or object');
+                    }
+
+                    $operations[$i][$type][1] = $args[1];
+
+                    break;
+
+                case self::REPLACE_ONE:
+                    if (! isset($args[1]) && ! array_key_exists(1, $args)) {
+                        throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
+                    }
+
+                    if (! is_array($args[1]) && ! is_object($args[1])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
+                    }
+
+                    // Treat empty arrays as replacement documents for BC
+                    if ($args[1] === []) {
+                        $args[1] = (object) $args[1];
+                    }
+
+                    if (is_first_key_operator($args[1])) {
+                        throw new InvalidArgumentException(sprintf('First key in $operations[%d]["%s"][1] is an update operator', $i, $type));
+                    }
+
+                    if (is_pipeline($args[1], true /* allowEmpty */)) {
+                        throw new InvalidArgumentException(sprintf('$operations[%d]["%s"][1] is an update pipeline', $i, $type));
+                    }
+
+                    if (! isset($args[2])) {
+                        $args[2] = [];
+                    }
+
+                    if (! is_array($args[2])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array');
+                    }
+
+                    $args[2]['multi'] = false;
+                    $args[2] += ['upsert' => false];
+
+                    if (isset($args[2]['collation']) && ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation'], 'array or object');
+                    }
+
+                    if (! is_bool($args[2]['upsert'])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
+                    }
+
+                    $operations[$i][$type][2] = $args[2];
+
+                    break;
+
+                case self::UPDATE_MANY:
+                case self::UPDATE_ONE:
+                    if (! isset($args[1]) && ! array_key_exists(1, $args)) {
+                        throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type));
+                    }
+
+                    if (! is_array($args[1]) && ! is_object($args[1])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][1]', $i, $type), $args[1], 'array or object');
+                    }
+
+                    if (! is_first_key_operator($args[1]) && ! is_pipeline($args[1])) {
+                        throw new InvalidArgumentException(sprintf('Expected update operator(s) or non-empty pipeline for $operations[%d]["%s"][1]', $i, $type));
+                    }
+
+                    if (! isset($args[2])) {
+                        $args[2] = [];
+                    }
+
+                    if (! is_array($args[2])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]', $i, $type), $args[2], 'array');
+                    }
+
+                    $args[2]['multi'] = ($type === self::UPDATE_MANY);
+                    $args[2] += ['upsert' => false];
+
+                    if (isset($args[2]['arrayFilters']) && ! is_array($args[2]['arrayFilters'])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["arrayFilters"]', $i, $type), $args[2]['arrayFilters'], 'array');
+                    }
+
+                    if (isset($args[2]['collation']) && ! is_array($args[2]['collation']) && ! is_object($args[2]['collation'])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation'], 'array or object');
+                    }
+
+                    if (! is_bool($args[2]['upsert'])) {
+                        throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
+                    }
+
+                    $operations[$i][$type][2] = $args[2];
+
+                    break;
+
+                default:
+                    throw new InvalidArgumentException(sprintf('Unknown operation type "%s" in $operations[%d]', $type, $i));
+            }
+        }
+
+        $options += ['ordered' => true];
+
+        if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) {
+            throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean');
+        }
+
+        if (! is_bool($options['ordered'])) {
+            throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
+        }
+
+        if (isset($options['let']) && ! is_array($options['let']) && ! is_object($options['let'])) {
+            throw InvalidArgumentException::invalidType('"let" option', $options['let'], 'array or object');
+        }
+
+        if (isset($options['bypassDocumentValidation']) && ! $options['bypassDocumentValidation']) {
+            unset($options['bypassDocumentValidation']);
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        $this->databaseName = $databaseName;
+        $this->collectionName = $collectionName;
+        $this->operations = $operations;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @return BulkWriteResult
+     * @throws UnsupportedException if write concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
+        if ($inTransaction && isset($this->options['writeConcern'])) {
+            throw UnsupportedException::writeConcernNotSupportedInTransaction();
+        }
+
+        $bulk = new Bulk($this->createBulkWriteOptions());
+        $insertedIds = [];
+
+        foreach ($this->operations as $i => $operation) {
+            $type = key($operation);
+            $args = current($operation);
+
+            switch ($type) {
+                case self::DELETE_MANY:
+                case self::DELETE_ONE:
+                    $bulk->delete($args[0], $args[1]);
+                    break;
+
+                case self::INSERT_ONE:
+                    $insertedIds[$i] = $bulk->insert($args[0]);
+                    break;
+
+                case self::REPLACE_ONE:
+                case self::UPDATE_MANY:
+                case self::UPDATE_ONE:
+                    $bulk->update($args[0], $args[1], $args[2]);
+            }
+        }
+
+        $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createExecuteOptions());
+
+        return new BulkWriteResult($writeResult, $insertedIds);
+    }
+
+    /**
+     * Create options for constructing the bulk write.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-bulkwrite.construct.php
+     */
+    private function createBulkWriteOptions(): array
+    {
+        $options = ['ordered' => $this->options['ordered']];
+
+        foreach (['bypassDocumentValidation', 'comment'] as $option) {
+            if (isset($this->options[$option])) {
+                $options[$option] = $this->options[$option];
+            }
+        }
+
+        if (isset($this->options['let'])) {
+            $options['let'] = (object) $this->options['let'];
+        }
+
+        return $options;
+    }
+
+    /**
+     * Create options for executing the bulk write.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-server.executebulkwrite.php
+     */
+    private function createExecuteOptions(): array
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if (isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }
+}

+ 236 - 0
api/v1/vendor/mongodb/mongodb/src/Operation/Count.php

@@ -0,0 +1,236 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\ReadConcern;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+
+use function current;
+use function is_array;
+use function is_float;
+use function is_integer;
+use function is_object;
+use function is_string;
+
+/**
+ * Operation for the count command.
+ *
+ * @see \MongoDB\Collection::count()
+ * @see https://mongodb.com/docs/manual/reference/command/count/
+ */
+class Count implements Executable, Explainable
+{
+    /** @var string */
+    private $databaseName;
+
+    /** @var string */
+    private $collectionName;
+
+    /** @var array|object */
+    private $filter;
+
+    /** @var array */
+    private $options;
+
+    /**
+     * Constructs a count command.
+     *
+     * Supported options:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *  * comment (mixed): BSON value to attach as a comment to this command.
+     *
+     *    This is not supported for servers versions < 4.4.
+     *
+     *  * hint (string|document): The index to use. Specify either the index
+     *    name as a string or the index key pattern as a document. If specified,
+     *    then the query system will only consider plans using the hinted index.
+     *
+     *  * limit (integer): The maximum number of documents to count.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *  * skip (integer): The number of documents to skip before returning the
+     *    documents.
+     *
+     * @param string       $databaseName   Database name
+     * @param string       $collectionName Collection name
+     * @param array|object $filter         Query by which to filter documents
+     * @param array        $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, string $collectionName, $filter = [], array $options = [])
+    {
+        if (! is_array($filter) && ! is_object($filter)) {
+            throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) {
+            throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object');
+        }
+
+        if (isset($options['limit']) && ! is_integer($options['limit'])) {
+            throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
+            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
+        }
+
+        if (isset($options['skip']) && ! is_integer($options['skip'])) {
+            throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
+        }
+
+        if (isset($options['readConcern']) && $options['readConcern']->isDefault()) {
+            unset($options['readConcern']);
+        }
+
+        $this->databaseName = $databaseName;
+        $this->collectionName = $collectionName;
+        $this->filter = $filter;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if read concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
+        if ($inTransaction && isset($this->options['readConcern'])) {
+            throw UnsupportedException::readConcernNotSupportedInTransaction();
+        }
+
+        $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
+        $result = current($cursor->toArray());
+
+        // Older server versions may return a float
+        if (! is_object($result) || ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
+            throw new UnexpectedValueException('count command did not return a numeric "n" value');
+        }
+
+        return (integer) $result->n;
+    }
+
+    /**
+     * Returns the command document for this operation.
+     *
+     * @see Explainable::getCommandDocument()
+     * @return array
+     */
+    public function getCommandDocument()
+    {
+        $cmd = $this->createCommandDocument();
+
+        // Read concern can change the query plan
+        if (isset($this->options['readConcern'])) {
+            $cmd['readConcern'] = $this->options['readConcern'];
+        }
+
+        return $cmd;
+    }
+
+    /**
+     * Create the count command document.
+     */
+    private function createCommandDocument(): array
+    {
+        $cmd = ['count' => $this->collectionName];
+
+        if (! empty($this->filter)) {
+            $cmd['query'] = (object) $this->filter;
+        }
+
+        if (isset($this->options['collation'])) {
+            $cmd['collation'] = (object) $this->options['collation'];
+        }
+
+        if (isset($this->options['hint'])) {
+            $cmd['hint'] = is_array($this->options['hint']) ? (object) $this->options['hint'] : $this->options['hint'];
+        }
+
+        foreach (['comment', 'limit', 'maxTimeMS', 'skip'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        return $cmd;
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-server.executereadcommand.php
+     */
+    private function createOptions(): array
+    {
+        $options = [];
+
+        if (isset($this->options['readConcern'])) {
+            $options['readConcern'] = $this->options['readConcern'];
+        }
+
+        if (isset($this->options['readPreference'])) {
+            $options['readPreference'] = $this->options['readPreference'];
+        }
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        return $options;
+    }
+}

+ 169 - 0
api/v1/vendor/mongodb/mongodb/src/Operation/CountDocuments.php

@@ -0,0 +1,169 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Server;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnexpectedValueException;
+use MongoDB\Exception\UnsupportedException;
+
+use function array_intersect_key;
+use function assert;
+use function count;
+use function current;
+use function is_array;
+use function is_float;
+use function is_integer;
+use function is_object;
+
+/**
+ * Operation for obtaining an exact count of documents in a collection
+ *
+ * @see \MongoDB\Collection::countDocuments()
+ * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#countdocuments
+ */
+class CountDocuments implements Executable
+{
+    /** @var string */
+    private $databaseName;
+
+    /** @var string */
+    private $collectionName;
+
+    /** @var array|object */
+    private $filter;
+
+    /** @var array */
+    private $aggregateOptions;
+
+    /** @var array */
+    private $countOptions;
+
+    /** @var Aggregate */
+    private $aggregate;
+
+    /**
+     * Constructs an aggregate command for counting documents
+     *
+     * Supported options:
+     *
+     *  * collation (document): Collation specification.
+     *
+     *  * comment (mixed): BSON value to attach as a comment to this command.
+     *
+     *    Only string values are supported for server versions < 4.4.
+     *
+     *  * hint (string|document): The index to use. Specify either the index
+     *    name as a string or the index key pattern as a document. If specified,
+     *    then the query system will only consider plans using the hinted index.
+     *
+     *  * limit (integer): The maximum number of documents to count.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * readConcern (MongoDB\Driver\ReadConcern): Read concern.
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): Read preference.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *  * skip (integer): The number of documents to skip before returning the
+     *    documents.
+     *
+     * @param string       $databaseName   Database name
+     * @param string       $collectionName Collection name
+     * @param array|object $filter         Query by which to filter documents
+     * @param array        $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, string $collectionName, $filter, array $options = [])
+    {
+        if (! is_array($filter) && ! is_object($filter)) {
+            throw InvalidArgumentException::invalidType('$filter', $filter, 'array or object');
+        }
+
+        if (isset($options['limit']) && ! is_integer($options['limit'])) {
+            throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer');
+        }
+
+        if (isset($options['skip']) && ! is_integer($options['skip'])) {
+            throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer');
+        }
+
+        $this->databaseName = $databaseName;
+        $this->collectionName = $collectionName;
+        $this->filter = $filter;
+
+        $this->aggregateOptions = array_intersect_key($options, ['collation' => 1, 'comment' => 1, 'hint' => 1, 'maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]);
+        $this->countOptions = array_intersect_key($options, ['limit' => 1, 'skip' => 1]);
+
+        $this->aggregate = $this->createAggregate();
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @return integer
+     * @throws UnexpectedValueException if the command response was malformed
+     * @throws UnsupportedException if collation or read concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        $cursor = $this->aggregate->execute($server);
+        assert($cursor instanceof Cursor);
+
+        $allResults = $cursor->toArray();
+
+        /* If there are no documents to count, the aggregation pipeline has no items to group, and
+         * hence the result is an empty array (PHPLIB-376) */
+        if (count($allResults) == 0) {
+            return 0;
+        }
+
+        $result = current($allResults);
+        if (! is_object($result) || ! isset($result->n) || ! (is_integer($result->n) || is_float($result->n))) {
+            throw new UnexpectedValueException('count command did not return a numeric "n" value');
+        }
+
+        return (integer) $result->n;
+    }
+
+    private function createAggregate(): Aggregate
+    {
+        $pipeline = [
+            ['$match' => (object) $this->filter],
+        ];
+
+        if (isset($this->countOptions['skip'])) {
+            $pipeline[] = ['$skip' => $this->countOptions['skip']];
+        }
+
+        if (isset($this->countOptions['limit'])) {
+            $pipeline[] = ['$limit' => $this->countOptions['limit']];
+        }
+
+        $pipeline[] = ['$group' => ['_id' => 1, 'n' => ['$sum' => 1]]];
+
+        return new Aggregate($this->databaseName, $this->collectionName, $pipeline, $this->aggregateOptions);
+    }
+}

+ 311 - 0
api/v1/vendor/mongodb/mongodb/src/Operation/CreateCollection.php

@@ -0,0 +1,311 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Exception\InvalidArgumentException;
+
+use function current;
+use function is_array;
+use function is_bool;
+use function is_integer;
+use function is_object;
+use function is_string;
+use function MongoDB\is_pipeline;
+use function trigger_error;
+
+use const E_USER_DEPRECATED;
+
+/**
+ * Operation for the create command.
+ *
+ * @see \MongoDB\Database::createCollection()
+ * @see https://mongodb.com/docs/manual/reference/command/create/
+ */
+class CreateCollection implements Executable
+{
+    public const USE_POWER_OF_2_SIZES = 1;
+    public const NO_PADDING = 2;
+
+    /** @var string */
+    private $databaseName;
+
+    /** @var string */
+    private $collectionName;
+
+    /** @var array */
+    private $options = [];
+
+    /**
+     * Constructs a create command.
+     *
+     * Supported options:
+     *
+     *  * autoIndexId (boolean): Specify false to disable the automatic creation
+     *    of an index on the _id field. For replica sets, this option cannot be
+     *    false. The default is true.
+     *
+     *    This option has been deprecated since MongoDB 3.2. As of MongoDB 4.0,
+     *    this option cannot be false when creating a replicated collection
+     *    (i.e. a collection outside of the local database in any mongod mode).
+     *
+     *  * capped (boolean): Specify true to create a capped collection. If set,
+     *    the size option must also be specified. The default is false.
+     *
+     *  * comment (mixed): BSON value to attach as a comment to this command.
+     *
+     *    This is not supported for servers versions < 4.4.
+     *
+     *  * changeStreamPreAndPostImages (document): Used to configure support for
+     *    pre- and post-images in change streams.
+     *
+     *    This is not supported for server versions < 6.0.
+     *
+     *  * clusteredIndex (document): A clustered index specification.
+     *
+     *    This is not supported for server versions < 5.3.
+     *
+     *  * collation (document): Collation specification.
+     *
+     *  * encryptedFields (document): Configuration for encrypted fields.
+     *    See: https://www.mongodb.com/docs/manual/core/queryable-encryption/fundamentals/encrypt-and-query/
+     *
+     *  * expireAfterSeconds: The TTL for documents in time series collections.
+     *
+     *    This is not supported for servers versions < 5.0.
+     *
+     *  * flags (integer): Options for the MMAPv1 storage engine only. Must be a
+     *    bitwise combination CreateCollection::USE_POWER_OF_2_SIZES and
+     *    CreateCollection::NO_PADDING. The default is
+     *    CreateCollection::USE_POWER_OF_2_SIZES.
+     *
+     *  * indexOptionDefaults (document): Default configuration for indexes when
+     *    creating the collection.
+     *
+     *  * max (integer): The maximum number of documents allowed in the capped
+     *    collection. The size option takes precedence over this limit.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * pipeline (array): An array that consists of the aggregation pipeline
+     *    stage(s), which will be applied to the collection or view specified by
+     *    viewOn.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *  * size (integer): The maximum number of bytes for a capped collection.
+     *
+     *  * storageEngine (document): Storage engine options.
+     *
+     *  * timeseries (document): Options for time series collections.
+     *
+     *    This is not supported for servers versions < 5.0.
+     *
+     *  * typeMap (array): Type map for BSON deserialization. This will only be
+     *    used for the returned command result document.
+     *
+     *  * validationAction (string): Validation action.
+     *
+     *  * validationLevel (string): Validation level.
+     *
+     *  * validator (document): Validation rules or expressions.
+     *
+     *  * viewOn (string): The name of the source collection or view from which
+     *    to create the view.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @see https://source.wiredtiger.com/2.4.1/struct_w_t___s_e_s_s_i_o_n.html#a358ca4141d59c345f401c58501276bbb
+     * @see https://mongodb.com/docs/manual/core/schema-validation/
+     * @param string $databaseName   Database name
+     * @param string $collectionName Collection name
+     * @param array  $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, string $collectionName, array $options = [])
+    {
+        if (isset($options['autoIndexId']) && ! is_bool($options['autoIndexId'])) {
+            throw InvalidArgumentException::invalidType('"autoIndexId" option', $options['autoIndexId'], 'boolean');
+        }
+
+        if (isset($options['capped']) && ! is_bool($options['capped'])) {
+            throw InvalidArgumentException::invalidType('"capped" option', $options['capped'], 'boolean');
+        }
+
+        if (isset($options['changeStreamPreAndPostImages']) && ! is_array($options['changeStreamPreAndPostImages']) && ! is_object($options['changeStreamPreAndPostImages'])) {
+            throw InvalidArgumentException::invalidType('"changeStreamPreAndPostImages" option', $options['changeStreamPreAndPostImages'], 'array or object');
+        }
+
+        if (isset($options['clusteredIndex']) && ! is_array($options['clusteredIndex']) && ! is_object($options['clusteredIndex'])) {
+            throw InvalidArgumentException::invalidType('"clusteredIndex" option', $options['clusteredIndex'], 'array or object');
+        }
+
+        if (isset($options['collation']) && ! is_array($options['collation']) && ! is_object($options['collation'])) {
+            throw InvalidArgumentException::invalidType('"collation" option', $options['collation'], 'array or object');
+        }
+
+        if (isset($options['encryptedFields']) && ! is_array($options['encryptedFields']) && ! is_object($options['encryptedFields'])) {
+            throw InvalidArgumentException::invalidType('"encryptedFields" option', $options['encryptedFields'], 'array or object');
+        }
+
+        if (isset($options['expireAfterSeconds']) && ! is_integer($options['expireAfterSeconds'])) {
+            throw InvalidArgumentException::invalidType('"expireAfterSeconds" option', $options['expireAfterSeconds'], 'integer');
+        }
+
+        if (isset($options['flags']) && ! is_integer($options['flags'])) {
+            throw InvalidArgumentException::invalidType('"flags" option', $options['flags'], 'integer');
+        }
+
+        if (isset($options['indexOptionDefaults']) && ! is_array($options['indexOptionDefaults']) && ! is_object($options['indexOptionDefaults'])) {
+            throw InvalidArgumentException::invalidType('"indexOptionDefaults" option', $options['indexOptionDefaults'], 'array or object');
+        }
+
+        if (isset($options['max']) && ! is_integer($options['max'])) {
+            throw InvalidArgumentException::invalidType('"max" option', $options['max'], 'integer');
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['pipeline']) && ! is_array($options['pipeline'])) {
+            throw InvalidArgumentException::invalidType('"pipeline" option', $options['pipeline'], 'array');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
+        }
+
+        if (isset($options['size']) && ! is_integer($options['size'])) {
+            throw InvalidArgumentException::invalidType('"size" option', $options['size'], 'integer');
+        }
+
+        if (isset($options['storageEngine']) && ! is_array($options['storageEngine']) && ! is_object($options['storageEngine'])) {
+            throw InvalidArgumentException::invalidType('"storageEngine" option', $options['storageEngine'], 'array or object');
+        }
+
+        if (isset($options['timeseries']) && ! is_array($options['timeseries']) && ! is_object($options['timeseries'])) {
+            throw InvalidArgumentException::invalidType('"timeseries" option', $options['timeseries'], ['array', 'object']);
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        if (isset($options['validationAction']) && ! is_string($options['validationAction'])) {
+            throw InvalidArgumentException::invalidType('"validationAction" option', $options['validationAction'], 'string');
+        }
+
+        if (isset($options['validationLevel']) && ! is_string($options['validationLevel'])) {
+            throw InvalidArgumentException::invalidType('"validationLevel" option', $options['validationLevel'], 'string');
+        }
+
+        if (isset($options['validator']) && ! is_array($options['validator']) && ! is_object($options['validator'])) {
+            throw InvalidArgumentException::invalidType('"validator" option', $options['validator'], 'array or object');
+        }
+
+        if (isset($options['viewOn']) && ! is_string($options['viewOn'])) {
+            throw InvalidArgumentException::invalidType('"viewOn" option', $options['viewOn'], 'string');
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        if (isset($options['autoIndexId'])) {
+            trigger_error('The "autoIndexId" option is deprecated and will be removed in a future release', E_USER_DEPRECATED);
+        }
+
+        if (isset($options['pipeline']) && ! is_pipeline($options['pipeline'], true /* allowEmpty */)) {
+            throw new InvalidArgumentException('"pipeline" option is not a valid aggregation pipeline');
+        }
+
+        $this->databaseName = $databaseName;
+        $this->collectionName = $collectionName;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @return array|object Command result document
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions());
+
+        if (isset($this->options['typeMap'])) {
+            $cursor->setTypeMap($this->options['typeMap']);
+        }
+
+        return current($cursor->toArray());
+    }
+
+    /**
+     * Create the create command.
+     */
+    private function createCommand(): Command
+    {
+        $cmd = ['create' => $this->collectionName];
+
+        foreach (['autoIndexId', 'capped', 'comment', 'expireAfterSeconds', 'flags', 'max', 'maxTimeMS', 'pipeline', 'size', 'validationAction', 'validationLevel', 'viewOn'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        foreach (['changeStreamPreAndPostImages', 'clusteredIndex', 'collation', 'encryptedFields', 'indexOptionDefaults', 'storageEngine', 'timeseries', 'validator'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = (object) $this->options[$option];
+            }
+        }
+
+        return new Command($cmd);
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-server.executewritecommand.php
+     */
+    private function createOptions(): array
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if (isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }
+}

+ 193 - 0
api/v1/vendor/mongodb/mongodb/src/Operation/CreateEncryptedCollection.php

@@ -0,0 +1,193 @@
+<?php
+/*
+ * Copyright 2023-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\BSON\Binary;
+use MongoDB\BSON\PackedArray;
+use MongoDB\BSON\Serializable;
+use MongoDB\Driver\ClientEncryption;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Server;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+
+use function array_key_exists;
+use function is_array;
+use function is_object;
+use function MongoDB\document_to_array;
+use function MongoDB\server_supports_feature;
+
+/**
+ * Create an encrypted collection.
+ *
+ * The "encryptedFields" option is required.
+ *
+ * This operation additionally creates related metadata collections and an index
+ * on the encrypted collection.
+ *
+ * @internal
+ * @see \MongoDB\Database::createCollection()
+ * @see \MongoDB\Database::createEncryptedCollection()
+ * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#create-collection-helper
+ * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#create-encrypted-collection-helper
+ * @see https://www.mongodb.com/docs/manual/core/queryable-encryption/fundamentals/manage-collections/
+ */
+class CreateEncryptedCollection implements Executable
+{
+    private const WIRE_VERSION_FOR_QUERYABLE_ENCRYPTION_V2 = 21;
+
+    /** @var CreateCollection */
+    private $createCollection;
+
+    /** @var CreateCollection[] */
+    private $createMetadataCollections;
+
+    /** @var CreateIndexes */
+    private $createSafeContentIndex;
+
+    /** @var string */
+    private $databaseName;
+
+    /** @var string */
+    private $collectionName;
+
+    /** @var array */
+    private $options;
+
+    /**
+     * @see CreateCollection::__construct() for supported options
+     * @param string $databaseName   Database name
+     * @param string $collectionName Collection name
+     * @param array  $options        CreateCollection options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, string $collectionName, array $options)
+    {
+        if (! isset($options['encryptedFields'])) {
+            throw new InvalidArgumentException('"encryptedFields" option is required');
+        }
+
+        if (! is_array($options['encryptedFields']) && ! is_object($options['encryptedFields'])) {
+            throw InvalidArgumentException::invalidType('"encryptedFields" option', $options['encryptedFields'], ['array', 'object']);
+        }
+
+        $this->createCollection = new CreateCollection($databaseName, $collectionName, $options);
+
+        /** @psalm-var array{ecocCollection?: ?string, escCollection?: ?string} */
+        $encryptedFields = document_to_array($options['encryptedFields']);
+        $enxcolOptions = ['clusteredIndex' => ['key' => ['_id' => 1], 'unique' => true]];
+
+        $this->createMetadataCollections = [
+            new CreateCollection($databaseName, $encryptedFields['escCollection'] ?? 'enxcol_.' . $collectionName . '.esc', $enxcolOptions),
+            new CreateCollection($databaseName, $encryptedFields['ecocCollection'] ?? 'enxcol_.' . $collectionName . '.ecoc', $enxcolOptions),
+        ];
+
+        $this->createSafeContentIndex = new CreateIndexes($databaseName, $collectionName, [['key' => ['__safeContent__' => 1]]]);
+
+        $this->databaseName = $databaseName;
+        $this->collectionName = $collectionName;
+        $this->options = $options;
+    }
+
+    /**
+     * Create data keys for any encrypted fields where "keyId" is null.
+     *
+     * This method should be called before execute(), as it may modify the
+     * "encryptedFields" option and reconstruct the internal CreateCollection
+     * operation used for creating the encrypted collection.
+     *
+     * The $encryptedFields reference parameter may be used to determine which
+     * data keys have been created.
+     *
+     * @see \MongoDB\Database::createEncryptedCollection()
+     * @see https://www.php.net/manual/en/mongodb-driver-clientencryption.createdatakey.php
+     * @throws DriverRuntimeException for errors creating a data key
+     */
+    public function createDataKeys(ClientEncryption $clientEncryption, string $kmsProvider, ?array $masterKey, ?array &$encryptedFields = null): void
+    {
+        /** @psalm-var array{fields: list<array{keyId: ?Binary}|object{keyId: ?Binary}>|Serializable|PackedArray} */
+        $encryptedFields = document_to_array($this->options['encryptedFields']);
+
+        // NOP if there are no fields to examine
+        if (! isset($encryptedFields['fields'])) {
+            return;
+        }
+
+        // Allow PackedArray or Serializable object for the fields array
+        if ($encryptedFields['fields'] instanceof PackedArray) {
+            /** @psalm-var array */
+            $encryptedFields['fields'] = $encryptedFields['fields']->toPHP([
+                'array' => 'array',
+                'document' => 'object',
+                'root' => 'array',
+            ]);
+        } elseif ($encryptedFields['fields'] instanceof Serializable) {
+            $encryptedFields['fields'] = $encryptedFields['fields']->bsonSerialize();
+        }
+
+        // Skip invalid types and defer to the server to raise an error
+        if (! is_array($encryptedFields['fields'])) {
+            return;
+        }
+
+        $createDataKeyArgs = [
+            $kmsProvider,
+            $masterKey !== null ? ['masterKey' => $masterKey] : [],
+        ];
+
+        foreach ($encryptedFields['fields'] as $i => $field) {
+            // Skip invalid types and defer to the server to raise an error
+            if (! is_array($field) && ! is_object($field)) {
+                continue;
+            }
+
+            $field = document_to_array($field);
+
+            if (array_key_exists('keyId', $field) && $field['keyId'] === null) {
+                $field['keyId'] = $clientEncryption->createDataKey(...$createDataKeyArgs);
+                $encryptedFields['fields'][$i] = $field;
+            }
+        }
+
+        $this->options['encryptedFields'] = $encryptedFields;
+        $this->createCollection = new CreateCollection($this->databaseName, $this->collectionName, $this->options);
+    }
+
+    /**
+     * @see Executable::execute()
+     * @return array|object Command result document from creating the encrypted collection
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     * @throws UnsupportedException if the server does not support Queryable Encryption
+     */
+    public function execute(Server $server)
+    {
+        if (! server_supports_feature($server, self::WIRE_VERSION_FOR_QUERYABLE_ENCRYPTION_V2)) {
+            throw new UnsupportedException('Driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption.');
+        }
+
+        foreach ($this->createMetadataCollections as $createMetadataCollection) {
+            $createMetadataCollection->execute($server);
+        }
+
+        $result = $this->createCollection->execute($server);
+
+        $this->createSafeContentIndex->execute($server);
+
+        return $result;
+    }
+}

+ 202 - 0
api/v1/vendor/mongodb/mongodb/src/Operation/CreateIndexes.php

@@ -0,0 +1,202 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Driver\WriteConcern;
+use MongoDB\Exception\InvalidArgumentException;
+use MongoDB\Exception\UnsupportedException;
+use MongoDB\Model\IndexInput;
+
+use function array_is_list;
+use function array_map;
+use function is_array;
+use function is_integer;
+use function is_string;
+use function MongoDB\server_supports_feature;
+use function sprintf;
+
+/**
+ * Operation for the createIndexes command.
+ *
+ * @see \MongoDB\Collection::createIndex()
+ * @see \MongoDB\Collection::createIndexes()
+ * @see https://mongodb.com/docs/manual/reference/command/createIndexes/
+ */
+class CreateIndexes implements Executable
+{
+    private const WIRE_VERSION_FOR_COMMIT_QUORUM = 9;
+
+    /** @var string */
+    private $databaseName;
+
+    /** @var string */
+    private $collectionName;
+
+    /** @var array */
+    private $indexes = [];
+
+    /** @var array */
+    private $options = [];
+
+    /**
+     * Constructs a createIndexes command.
+     *
+     * Supported options:
+     *
+     *  * comment (mixed): BSON value to attach as a comment to this command.
+     *
+     *    This is not supported for servers versions < 4.4.
+     *
+     *  * commitQuorum (integer|string): Specifies how many data-bearing members
+     *    of a replica set, including the primary, must complete the index
+     *    builds successfully before the primary marks the indexes as ready.
+     *
+     *  * maxTimeMS (integer): The maximum amount of time to allow the query to
+     *    run.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *  * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
+     *
+     * @param string  $databaseName   Database name
+     * @param string  $collectionName Collection name
+     * @param array[] $indexes        List of index specifications
+     * @param array   $options        Command options
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, string $collectionName, array $indexes, array $options = [])
+    {
+        if (empty($indexes)) {
+            throw new InvalidArgumentException('$indexes is empty');
+        }
+
+        if (! array_is_list($indexes)) {
+            throw new InvalidArgumentException('$indexes is not a list');
+        }
+
+        foreach ($indexes as $i => $index) {
+            if (! is_array($index)) {
+                throw InvalidArgumentException::invalidType(sprintf('$index[%d]', $i), $index, 'array');
+            }
+
+            $this->indexes[] = new IndexInput($index);
+        }
+
+        if (isset($options['commitQuorum']) && ! is_string($options['commitQuorum']) && ! is_integer($options['commitQuorum'])) {
+            throw InvalidArgumentException::invalidType('"commitQuorum" option', $options['commitQuorum'], ['integer', 'string']);
+        }
+
+        if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
+            throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
+        }
+
+        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
+            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class);
+        }
+
+        if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) {
+            unset($options['writeConcern']);
+        }
+
+        $this->databaseName = $databaseName;
+        $this->collectionName = $collectionName;
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @return string[] The names of the created indexes
+     * @throws UnsupportedException if write concern is used and unsupported
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    public function execute(Server $server)
+    {
+        $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
+        if ($inTransaction && isset($this->options['writeConcern'])) {
+            throw UnsupportedException::writeConcernNotSupportedInTransaction();
+        }
+
+        $this->executeCommand($server);
+
+        return array_map(function (IndexInput $index) {
+            return (string) $index;
+        }, $this->indexes);
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-server.executewritecommand.php
+     */
+    private function createOptions(): array
+    {
+        $options = [];
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        if (isset($this->options['writeConcern'])) {
+            $options['writeConcern'] = $this->options['writeConcern'];
+        }
+
+        return $options;
+    }
+
+    /**
+     * Create one or more indexes for the collection using the createIndexes
+     * command.
+     *
+     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
+     */
+    private function executeCommand(Server $server): void
+    {
+        $cmd = [
+            'createIndexes' => $this->collectionName,
+            'indexes' => $this->indexes,
+        ];
+
+        if (isset($this->options['commitQuorum'])) {
+            /* Drivers MUST manually raise an error if this option is specified
+             * when creating an index on a pre 4.4 server. */
+            if (! server_supports_feature($server, self::WIRE_VERSION_FOR_COMMIT_QUORUM)) {
+                throw UnsupportedException::commitQuorumNotSupported();
+            }
+
+            $cmd['commitQuorum'] = $this->options['commitQuorum'];
+        }
+
+        foreach (['comment', 'maxTimeMS'] as $option) {
+            if (isset($this->options[$option])) {
+                $cmd[$option] = $this->options[$option];
+            }
+        }
+
+        $server->executeWriteCommand($this->databaseName, new Command($cmd), $this->createOptions());
+    }
+}

+ 126 - 0
api/v1/vendor/mongodb/mongodb/src/Operation/DatabaseCommand.php

@@ -0,0 +1,126 @@
+<?php
+/*
+ * Copyright 2015-present MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace MongoDB\Operation;
+
+use MongoDB\Driver\Command;
+use MongoDB\Driver\Cursor;
+use MongoDB\Driver\ReadPreference;
+use MongoDB\Driver\Server;
+use MongoDB\Driver\Session;
+use MongoDB\Exception\InvalidArgumentException;
+
+use function is_array;
+use function is_object;
+
+/**
+ * Operation for executing a database command.
+ *
+ * @see \MongoDB\Database::command()
+ */
+class DatabaseCommand implements Executable
+{
+    /** @var string */
+    private $databaseName;
+
+    /** @var Command */
+    private $command;
+
+    /** @var array */
+    private $options;
+
+    /**
+     * Constructs a command.
+     *
+     * Supported options:
+     *
+     *  * readPreference (MongoDB\Driver\ReadPreference): The read preference to
+     *    use when executing the command. This may be used when issuing the
+     *    command to a replica set or mongos node to ensure that the driver sets
+     *    the wire protocol accordingly or adds the read preference to the
+     *    command document, respectively.
+     *
+     *  * session (MongoDB\Driver\Session): Client session.
+     *
+     *  * typeMap (array): Type map for BSON deserialization. This will be
+     *    applied to the returned Cursor (it is not sent to the server).
+     *
+     * @param string       $databaseName Database name
+     * @param array|object $command      Command document
+     * @param array        $options      Options for command execution
+     * @throws InvalidArgumentException for parameter/option parsing errors
+     */
+    public function __construct(string $databaseName, $command, array $options = [])
+    {
+        if (! is_array($command) && ! is_object($command)) {
+            throw InvalidArgumentException::invalidType('$command', $command, 'array or object');
+        }
+
+        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
+            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
+        }
+
+        if (isset($options['session']) && ! $options['session'] instanceof Session) {
+            throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class);
+        }
+
+        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
+            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
+        }
+
+        $this->databaseName = $databaseName;
+        $this->command = $command instanceof Command ? $command : new Command($command);
+        $this->options = $options;
+    }
+
+    /**
+     * Execute the operation.
+     *
+     * @see Executable::execute()
+     * @return Cursor
+     */
+    public function execute(Server $server)
+    {
+        $cursor = $server->executeCommand($this->databaseName, $this->command, $this->createOptions());
+
+        if (isset($this->options['typeMap'])) {
+            $cursor->setTypeMap($this->options['typeMap']);
+        }
+
+        return $cursor;
+    }
+
+    /**
+     * Create options for executing the command.
+     *
+     * @see https://php.net/manual/en/mongodb-driver-server.executecommand.php
+     */
+    private function createOptions(): array
+    {
+        $options = [];
+
+        if (isset($this->options['readPreference'])) {
+            $options['readPreference'] = $this->options['readPreference'];
+        }
+
+        if (isset($this->options['session'])) {
+            $options['session'] = $this->options['session'];
+        }
+
+        return $options;
+    }
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio