Compare commits
9 Commits
b6d1215999
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 248698fda3 | |||
| f96c3e9337 | |||
| 4051674324 | |||
| b8446181d3 | |||
| 141deaa35b | |||
| 03fd8a7df7 | |||
| 72674c6592 | |||
| bcbf807aac | |||
| d405d58f8d |
96
.gitignore
vendored
96
.gitignore
vendored
@@ -1,89 +1,7 @@
|
||||
vendor/autoload.php
|
||||
vendor/clue/ndjson-react/CHANGELOG.md
|
||||
vendor/clue/ndjson-react/composer.json
|
||||
vendor/clue/ndjson-react/LICENSE
|
||||
vendor/clue/ndjson-react/README.md
|
||||
vendor/clue/ndjson-react/.github/FUNDING.yml
|
||||
vendor/clue/ndjson-react/src/Decoder.php
|
||||
vendor/clue/ndjson-react/src/Encoder.php
|
||||
vendor/clue/reactphp-sqlite/CHANGELOG.md
|
||||
vendor/clue/reactphp-sqlite/composer.json
|
||||
vendor/clue/reactphp-sqlite/LICENSE
|
||||
vendor/clue/reactphp-sqlite/README.md
|
||||
vendor/clue/reactphp-sqlite/.github/FUNDING.yml
|
||||
vendor/clue/reactphp-sqlite/res/sqlite-worker.php
|
||||
vendor/clue/reactphp-sqlite/src/DatabaseInterface.php
|
||||
vendor/clue/reactphp-sqlite/src/Factory.php
|
||||
vendor/clue/reactphp-sqlite/src/Result.php
|
||||
vendor/clue/reactphp-sqlite/src/Io/BlockingDatabase.php
|
||||
vendor/clue/reactphp-sqlite/src/Io/LazyDatabase.php
|
||||
vendor/clue/reactphp-sqlite/src/Io/ProcessIoDatabase.php
|
||||
vendor/composer/autoload_classmap.php
|
||||
vendor/composer/autoload_files.php
|
||||
vendor/composer/autoload_namespaces.php
|
||||
vendor/composer/autoload_psr4.php
|
||||
vendor/composer/autoload_real.php
|
||||
vendor/composer/autoload_static.php
|
||||
vendor/composer/ClassLoader.php
|
||||
vendor/composer/installed.json
|
||||
vendor/composer/installed.php
|
||||
vendor/composer/InstalledVersions.php
|
||||
vendor/composer/LICENSE
|
||||
vendor/composer/platform_check.php
|
||||
vendor/evenement/evenement/.gitattributes
|
||||
vendor/evenement/evenement/composer.json
|
||||
vendor/evenement/evenement/LICENSE
|
||||
vendor/evenement/evenement/README.md
|
||||
vendor/evenement/evenement/src/EventEmitter.php
|
||||
vendor/evenement/evenement/src/EventEmitterInterface.php
|
||||
vendor/evenement/evenement/src/EventEmitterTrait.php
|
||||
vendor/react/child-process/CHANGELOG.md
|
||||
vendor/react/child-process/composer.json
|
||||
vendor/react/child-process/LICENSE
|
||||
vendor/react/child-process/README.md
|
||||
vendor/react/child-process/src/Process.php
|
||||
vendor/react/event-loop/CHANGELOG.md
|
||||
vendor/react/event-loop/composer.json
|
||||
vendor/react/event-loop/LICENSE
|
||||
vendor/react/event-loop/README.md
|
||||
vendor/react/event-loop/src/ExtEventLoop.php
|
||||
vendor/react/event-loop/src/ExtEvLoop.php
|
||||
vendor/react/event-loop/src/ExtLibeventLoop.php
|
||||
vendor/react/event-loop/src/ExtLibevLoop.php
|
||||
vendor/react/event-loop/src/ExtUvLoop.php
|
||||
vendor/react/event-loop/src/Factory.php
|
||||
vendor/react/event-loop/src/Loop.php
|
||||
vendor/react/event-loop/src/LoopInterface.php
|
||||
vendor/react/event-loop/src/SignalsHandler.php
|
||||
vendor/react/event-loop/src/StreamSelectLoop.php
|
||||
vendor/react/event-loop/src/TimerInterface.php
|
||||
vendor/react/event-loop/src/Tick/FutureTickQueue.php
|
||||
vendor/react/event-loop/src/Timer/Timer.php
|
||||
vendor/react/event-loop/src/Timer/Timers.php
|
||||
vendor/react/promise/CHANGELOG.md
|
||||
vendor/react/promise/composer.json
|
||||
vendor/react/promise/LICENSE
|
||||
vendor/react/promise/README.md
|
||||
vendor/react/promise/src/Deferred.php
|
||||
vendor/react/promise/src/functions_include.php
|
||||
vendor/react/promise/src/functions.php
|
||||
vendor/react/promise/src/Promise.php
|
||||
vendor/react/promise/src/PromiseInterface.php
|
||||
vendor/react/promise/src/Exception/CompositeException.php
|
||||
vendor/react/promise/src/Exception/LengthException.php
|
||||
vendor/react/promise/src/Internal/CancellationQueue.php
|
||||
vendor/react/promise/src/Internal/FulfilledPromise.php
|
||||
vendor/react/promise/src/Internal/RejectedPromise.php
|
||||
vendor/react/stream/CHANGELOG.md
|
||||
vendor/react/stream/composer.json
|
||||
vendor/react/stream/LICENSE
|
||||
vendor/react/stream/README.md
|
||||
vendor/react/stream/src/CompositeStream.php
|
||||
vendor/react/stream/src/DuplexResourceStream.php
|
||||
vendor/react/stream/src/DuplexStreamInterface.php
|
||||
vendor/react/stream/src/ReadableResourceStream.php
|
||||
vendor/react/stream/src/ReadableStreamInterface.php
|
||||
vendor/react/stream/src/ThroughStream.php
|
||||
vendor/react/stream/src/Util.php
|
||||
vendor/react/stream/src/WritableResourceStream.php
|
||||
vendor/react/stream/src/WritableStreamInterface.php
|
||||
/vendor
|
||||
.env
|
||||
composer.lock
|
||||
requests.sqlite3
|
||||
requests.sqlite3-shm
|
||||
requests.sqlite3-wal
|
||||
GeoLite2-City.mmdb
|
||||
@@ -1,6 +1,18 @@
|
||||
{
|
||||
"require": {
|
||||
"clue/reactphp-sqlite": "^1.6",
|
||||
"clue/framework-x": "^0.16"
|
||||
"clue/framework-x": "^0.16",
|
||||
"vlucas/phpdotenv": "^5.6",
|
||||
"react/cache": "^1.2",
|
||||
"clue/mq-react": "^1.6",
|
||||
"smarty/smarty": "^5.4",
|
||||
"react/promise-timer": "^1.11",
|
||||
"geoip2/geoip2": "~2.0",
|
||||
"maxmind-db/reader": "~1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"XBotControl\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
930
composer.lock
generated
930
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "db1acaba19a0c8b768f5023662d59dc1",
|
||||
"content-hash": "d6f1f7467007dba97d491d18cbdfc5d6",
|
||||
"packages": [
|
||||
{
|
||||
"name": "clue/framework-x",
|
||||
@@ -78,6 +78,77 @@
|
||||
],
|
||||
"time": "2024-03-05T14:41:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "clue/mq-react",
|
||||
"version": "v1.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/clue/reactphp-mq.git",
|
||||
"reference": "cab0147723017bc2deb3f248c607ad8e3c87e509"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/clue/reactphp-mq/zipball/cab0147723017bc2deb3f248c607ad8e3c87e509",
|
||||
"reference": "cab0147723017bc2deb3f248c607ad8e3c87e509",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": "^3 || ^2.2.1 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
|
||||
"react/async": "^4 || ^3 || ^2",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/http": "^1.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Clue\\React\\Mq\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"description": "Mini Queue, the lightweight in-memory message queue to concurrently do many (but not too many) things at once, built on top of ReactPHP",
|
||||
"homepage": "https://github.com/clue/reactphp-mq",
|
||||
"keywords": [
|
||||
"Mini Queue",
|
||||
"async",
|
||||
"concurrency",
|
||||
"job",
|
||||
"message",
|
||||
"message queue",
|
||||
"queue",
|
||||
"rate limit",
|
||||
"reactphp",
|
||||
"throttle",
|
||||
"worker"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/clue/reactphp-mq/issues",
|
||||
"source": "https://github.com/clue/reactphp-mq/tree/v1.6.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://clue.engineering/support",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/clue",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-28T14:12:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "clue/ndjson-react",
|
||||
"version": "v1.3.0",
|
||||
@@ -208,6 +279,82 @@
|
||||
],
|
||||
"time": "2023-05-12T12:33:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
"version": "1.5.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/ca-bundle.git",
|
||||
"reference": "bc0593537a463e55cadf45fd938d23b75095b7e1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/bc0593537a463e55cadf45fd938d23b75095b7e1",
|
||||
"reference": "bc0593537a463e55cadf45fd938d23b75095b7e1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-openssl": "*",
|
||||
"ext-pcre": "*",
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^8 || ^9",
|
||||
"psr/log": "^1.0 || ^2.0 || ^3.0",
|
||||
"symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Composer\\CaBundle\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jordi Boggiano",
|
||||
"email": "j.boggiano@seld.be",
|
||||
"homepage": "http://seld.be"
|
||||
}
|
||||
],
|
||||
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
|
||||
"keywords": [
|
||||
"cabundle",
|
||||
"cacert",
|
||||
"certificate",
|
||||
"ssl",
|
||||
"tls"
|
||||
],
|
||||
"support": {
|
||||
"irc": "irc://irc.freenode.org/composer",
|
||||
"issues": "https://github.com/composer/ca-bundle/issues",
|
||||
"source": "https://github.com/composer/ca-bundle/tree/1.5.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-11-27T15:35:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "evenement/evenement",
|
||||
"version": "v3.0.2",
|
||||
@@ -311,6 +458,240 @@
|
||||
},
|
||||
"time": "2020-11-24T22:02:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "geoip2/geoip2",
|
||||
"version": "v2.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maxmind/GeoIP2-php.git",
|
||||
"reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/6a41d8fbd6b90052bc34dff3b4252d0f88067b23",
|
||||
"reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"maxmind-db/reader": "~1.8",
|
||||
"maxmind/web-service-common": "~0.8",
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.*",
|
||||
"phpstan/phpstan": "*",
|
||||
"phpunit/phpunit": "^8.0 || ^9.0",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GeoIp2\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gregory J. Oschwald",
|
||||
"email": "goschwald@maxmind.com",
|
||||
"homepage": "https://www.maxmind.com/"
|
||||
}
|
||||
],
|
||||
"description": "MaxMind GeoIP2 PHP API",
|
||||
"homepage": "https://github.com/maxmind/GeoIP2-php",
|
||||
"keywords": [
|
||||
"IP",
|
||||
"geoip",
|
||||
"geoip2",
|
||||
"geolocation",
|
||||
"maxmind"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maxmind/GeoIP2-php/issues",
|
||||
"source": "https://github.com/maxmind/GeoIP2-php/tree/v2.13.0"
|
||||
},
|
||||
"time": "2022-08-05T20:32:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GrahamCampbell/Result-Type.git",
|
||||
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||
"reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GrahamCampbell\\ResultType\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "An Implementation Of The Result Type",
|
||||
"keywords": [
|
||||
"Graham Campbell",
|
||||
"GrahamCampbell",
|
||||
"Result Type",
|
||||
"Result-Type",
|
||||
"result"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
|
||||
"source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:45:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
"version": "v1.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
|
||||
"reference": "5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90",
|
||||
"reference": "5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"conflict": {
|
||||
"ext-maxminddb": "<1.11.1 || >=2.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.*",
|
||||
"phpstan/phpstan": "*",
|
||||
"phpunit/phpunit": ">=8.0.0,<10.0.0",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
|
||||
"ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
|
||||
"ext-maxminddb": "A C-based database decoder that provides significantly faster lookups"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MaxMind\\Db\\": "src/MaxMind/Db"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gregory J. Oschwald",
|
||||
"email": "goschwald@maxmind.com",
|
||||
"homepage": "https://www.maxmind.com/"
|
||||
}
|
||||
],
|
||||
"description": "MaxMind DB Reader API",
|
||||
"homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
|
||||
"keywords": [
|
||||
"database",
|
||||
"geoip",
|
||||
"geoip2",
|
||||
"geolocation",
|
||||
"maxmind"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
|
||||
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.12.0"
|
||||
},
|
||||
"time": "2024-11-14T22:43:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maxmind/web-service-common",
|
||||
"version": "v0.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maxmind/web-service-common-php.git",
|
||||
"reference": "d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4",
|
||||
"reference": "d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer/ca-bundle": "^1.0.3",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.*",
|
||||
"phpstan/phpstan": "*",
|
||||
"phpunit/phpunit": "^8.0 || ^9.0",
|
||||
"squizlabs/php_codesniffer": "3.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MaxMind\\Exception\\": "src/Exception",
|
||||
"MaxMind\\WebService\\": "src/WebService"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gregory Oschwald",
|
||||
"email": "goschwald@maxmind.com"
|
||||
}
|
||||
],
|
||||
"description": "Internal MaxMind Web Service API",
|
||||
"homepage": "https://github.com/maxmind/web-service-common-php",
|
||||
"support": {
|
||||
"issues": "https://github.com/maxmind/web-service-common-php/issues",
|
||||
"source": "https://github.com/maxmind/web-service-common-php/tree/v0.10.0"
|
||||
},
|
||||
"time": "2024-11-14T23:14:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/fast-route",
|
||||
"version": "v1.3.0",
|
||||
@@ -361,6 +742,81 @@
|
||||
},
|
||||
"time": "2018-02-13T20:26:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/schmittjoh/php-option.git",
|
||||
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
|
||||
"reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "1.9-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOption\\": "src/PhpOption/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Apache-2.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Johannes M. Schmitt",
|
||||
"email": "schmittjoh@gmail.com",
|
||||
"homepage": "https://github.com/schmittjoh"
|
||||
},
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
}
|
||||
],
|
||||
"description": "Option Type for PHP",
|
||||
"keywords": [
|
||||
"language",
|
||||
"option",
|
||||
"php",
|
||||
"type"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/schmittjoh/php-option/issues",
|
||||
"source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:41:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "1.1",
|
||||
@@ -952,6 +1408,85 @@
|
||||
],
|
||||
"time": "2024-05-24T10:39:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/promise-timer",
|
||||
"version": "v1.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/promise-timer.git",
|
||||
"reference": "4f70306ed66b8b44768941ca7f142092600fafc1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/promise-timer/zipball/4f70306ed66b8b44768941ca7f142092600fafc1",
|
||||
"reference": "4f70306ed66b8b44768941ca7f142092600fafc1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/event-loop": "^1.2",
|
||||
"react/promise": "^3.2 || ^2.7.0 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"React\\Promise\\Timer\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering",
|
||||
"homepage": "https://clue.engineering/"
|
||||
},
|
||||
{
|
||||
"name": "Cees-Jan Kiewiet",
|
||||
"email": "reactphp@ceesjankiewiet.nl",
|
||||
"homepage": "https://wyrihaximus.net/"
|
||||
},
|
||||
{
|
||||
"name": "Jan Sorgalla",
|
||||
"email": "jsorgalla@gmail.com",
|
||||
"homepage": "https://sorgalla.com/"
|
||||
},
|
||||
{
|
||||
"name": "Chris Boden",
|
||||
"email": "cboden@gmail.com",
|
||||
"homepage": "https://cboden.dev/"
|
||||
}
|
||||
],
|
||||
"description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.",
|
||||
"homepage": "https://github.com/reactphp/promise-timer",
|
||||
"keywords": [
|
||||
"async",
|
||||
"event-loop",
|
||||
"promise",
|
||||
"reactphp",
|
||||
"timeout",
|
||||
"timer"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/reactphp/promise-timer/issues",
|
||||
"source": "https://github.com/reactphp/promise-timer/tree/v1.11.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/reactphp",
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2024-06-04T14:27:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/socket",
|
||||
"version": "v1.16.0",
|
||||
@@ -1109,6 +1644,399 @@
|
||||
}
|
||||
],
|
||||
"time": "2024-06-11T12:45:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "smarty/smarty",
|
||||
"version": "v5.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/smarty-php/smarty.git",
|
||||
"reference": "642a97adcc2bf6c1b2458d6afeeb36ae001c1c2f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/642a97adcc2bf6c1b2458d6afeeb36ae001c1c2f",
|
||||
"reference": "642a97adcc2bf6c1b2458d6afeeb36ae001c1c2f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0",
|
||||
"symfony/polyfill-mbstring": "^1.27"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5 || ^7.5",
|
||||
"smarty/smarty-lexer": "^4.0.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Smarty\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-3.0"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Monte Ohrt",
|
||||
"email": "monte@ohrt.com"
|
||||
},
|
||||
{
|
||||
"name": "Uwe Tews",
|
||||
"email": "uwe.tews@googlemail.com"
|
||||
},
|
||||
{
|
||||
"name": "Rodney Rehm",
|
||||
"email": "rodney.rehm@medialize.de"
|
||||
},
|
||||
{
|
||||
"name": "Simon Wisselink",
|
||||
"homepage": "https://www.iwink.nl/"
|
||||
}
|
||||
],
|
||||
"description": "Smarty - the compiling PHP template engine",
|
||||
"homepage": "https://smarty-php.github.io/smarty/",
|
||||
"keywords": [
|
||||
"templating"
|
||||
],
|
||||
"support": {
|
||||
"forum": "https://github.com/smarty-php/smarty/discussions",
|
||||
"issues": "https://github.com/smarty-php/smarty/issues",
|
||||
"source": "https://github.com/smarty-php/smarty/tree/v5.4.2"
|
||||
},
|
||||
"time": "2024-11-20T21:18:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Ctype\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gert de Pagter",
|
||||
"email": "BackEndTea@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for ctype functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"ctype",
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||
}
|
||||
},
|
||||
"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 for the Mbstring extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"mbstring",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.31.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "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.31.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vlucas/phpdotenv.git",
|
||||
"reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2",
|
||||
"reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-pcre": "*",
|
||||
"graham-campbell/result-type": "^1.1.3",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"phpoption/phpoption": "^1.9.3",
|
||||
"symfony/polyfill-ctype": "^1.24",
|
||||
"symfony/polyfill-mbstring": "^1.24",
|
||||
"symfony/polyfill-php80": "^1.24"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-filter": "*",
|
||||
"phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-filter": "Required to use the boolean validator."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "5.6-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dotenv\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Vance Lucas",
|
||||
"email": "vance@vancelucas.com",
|
||||
"homepage": "https://github.com/vlucas"
|
||||
}
|
||||
],
|
||||
"description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
|
||||
"keywords": [
|
||||
"dotenv",
|
||||
"env",
|
||||
"environment"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/vlucas/phpdotenv/issues",
|
||||
"source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-20T21:52:34+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
/* Smarty version 5.4.2, created on 2024-12-18 20:10:06
|
||||
from 'file:index.tpl' */
|
||||
|
||||
/* @var \Smarty\Template $_smarty_tpl */
|
||||
if ($_smarty_tpl->getCompiled()->isFresh($_smarty_tpl, array (
|
||||
'version' => '5.4.2',
|
||||
'unifunc' => 'content_67632c1ed9f985_55329758',
|
||||
'has_nocache_code' => false,
|
||||
'file_dependency' =>
|
||||
array (
|
||||
'12e7c104d0458c0f98059f5061a369703f954f4a' =>
|
||||
array (
|
||||
0 => 'index.tpl',
|
||||
1 => 1734552581,
|
||||
2 => 'file',
|
||||
),
|
||||
),
|
||||
'includes' =>
|
||||
array (
|
||||
),
|
||||
))) {
|
||||
function content_67632c1ed9f985_55329758 (\Smarty\Template $_smarty_tpl) {
|
||||
$_smarty_current_dir = '/home/upw/clients/kpopping/xbotcontrol/smarty/template';
|
||||
?><!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>XBotControl</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous">
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.css">
|
||||
<?php echo '<script'; ?>
|
||||
src="https://code.jquery.com/jquery-3.7.1.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<!-- Latest compiled and minified Locales -->
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/locale/bootstrap-table-en-US.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.css">
|
||||
<?php echo '<script'; ?>
|
||||
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.js">
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main-menu " style="background-color: #003366;">
|
||||
|
||||
<div class="container text-center text-light">
|
||||
<nav class="navbar navbar-expand-lg text-light">
|
||||
<div class="container">
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('latest_requests');">Latest</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('count_requests_by_ip');">Top by
|
||||
IP</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('count_requests_by_ua');">Top by
|
||||
UA</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_ua_path');">IP+UA+Path</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_by_load');">IP+Load</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_by_rps');">IP+RPS</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="content">
|
||||
<div id="main-body">
|
||||
<div class="container" style="max-width: 95%;">
|
||||
<div class="row p-3">
|
||||
<div class="col-10">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="toolbar" class="row ">
|
||||
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">Limit</div>
|
||||
|
||||
|
||||
<select id="limit" name="limit" class="form-control mr-3">
|
||||
<option value="10">10</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100" selected>100</option>
|
||||
<option value="200">200</option>
|
||||
<option value="500">500</option>
|
||||
<option value="1000">1000</option>
|
||||
<option value="100000">100000</option>
|
||||
<option value="0">0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">From</div>
|
||||
|
||||
<input type="datetime-local" id="date-from" name="date-from" class="form-control mr-3">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">To</div>
|
||||
|
||||
<input type="datetime-local" id="date-to" name="date-to" class="form-control mr-3">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<table id="table">
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php echo '<script'; ?>
|
||||
>
|
||||
document.getElementById('date-from').addEventListener('change', refreshTable);
|
||||
|
||||
document.getElementById('limit').addEventListener('change', refreshTable);
|
||||
|
||||
function refreshTable() {
|
||||
$('#table').bootstrapTable('refresh');
|
||||
}
|
||||
window.onload = function() {
|
||||
const dateFrom = document.getElementById('date-from');
|
||||
const dateTo = document.getElementById('date-to');
|
||||
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(today.getDate() - 1);
|
||||
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
|
||||
dateFrom.value = yesterday.toISOString().slice(0, 16);
|
||||
dateTo.value = tomorrow.toISOString().slice(0, 16);
|
||||
};
|
||||
document.getElementById('date-to').addEventListener('change', refreshTable);
|
||||
|
||||
|
||||
function initializeTable(latest_requests) {
|
||||
var url = location.pathname + '/api/report/' + latest_requests;
|
||||
var $table = $('#table');
|
||||
|
||||
if ($table.length) {
|
||||
$table.bootstrapTable('destroy');
|
||||
}
|
||||
|
||||
$.get(url, function(response) {
|
||||
$table.bootstrapTable({
|
||||
url: url,
|
||||
sortable: true,
|
||||
toolbar: '#toolbar',
|
||||
showRefresh: true,
|
||||
iconsPrefix: 'fa',
|
||||
showColumns: true,
|
||||
classes: ['table', 'table-borderless', 'table-hover', 'table-striped'],
|
||||
filterControl: true,
|
||||
searchable: true,
|
||||
pagination: false,
|
||||
sidePagination: "server",
|
||||
serverSort: false,
|
||||
columns: response.columns,
|
||||
queryParams: queryParams,
|
||||
loadingFontSize: '12px'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function queryParams(params) {
|
||||
const limit = document.getElementById('limit').value;
|
||||
const from = document.getElementById('date-from').value;
|
||||
const to = document.getElementById('date-to').value;
|
||||
|
||||
params.limit = limit;
|
||||
params.from = from;
|
||||
params.to = to;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function listFormatter(value, row, index) {
|
||||
var editBtn = '<a class="btn" href="<?php echo htmlspecialchars((string) ($_ENV['BASEURI']), ENT_QUOTES, 'UTF-8');?>
|
||||
/lists/edit/' + row.list_id + '" title="Edit"><i class="fa-solid fa-pen-to-square"></i></a> ';
|
||||
|
||||
var showBtn = '<a class="btn" href="<?php echo htmlspecialchars((string) ($_ENV['BASEURI']), ENT_QUOTES, 'UTF-8');?>
|
||||
/lists/show/' + row.list_id + '" title="Show"><i class="fa-solid fa-eye"></i></a>';
|
||||
<?php if ($_SESSION['user_role'] == 'admin') {?>
|
||||
return [showBtn, editBtn, value, ].join('')
|
||||
<?php } else { ?>
|
||||
return [showBtn, value, ].join('')
|
||||
|
||||
<?php }?>
|
||||
}
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
|
||||
<?php echo '<script'; ?>
|
||||
>
|
||||
function ipFormatter(value) {
|
||||
return `<span class="ip-address" data-ip="${value}">${value}</span>`;
|
||||
}
|
||||
|
||||
document.addEventListener('mouseover', async (event) => {
|
||||
const target = event.target;
|
||||
if (target.classList.contains('ip-address')) {
|
||||
const ipAddress = target.getAttribute('data-ip');
|
||||
const popupId = `popup-${ipAddress.replace(/\./g, '-')}`;
|
||||
let popup = document.getElementById(popupId);
|
||||
|
||||
if (!popup) {
|
||||
popup = document.createElement('div');
|
||||
popup.id = popupId;
|
||||
popup.style.position = 'absolute';
|
||||
popup.style.background = '#f9f9f9';
|
||||
popup.style.border = '1px solid #ccc';
|
||||
popup.style.padding = '10px';
|
||||
popup.style.borderRadius = '5px';
|
||||
popup.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
|
||||
popup.style.zIndex = '1000';
|
||||
popup.style.whiteSpace = 'nowrap';
|
||||
popup.style.display = 'none';
|
||||
document.body.appendChild(popup);
|
||||
|
||||
fetch(location.pathname + `/api/ipinfo/${ipAddress}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const location = data.geo.continent?.names?.en + ' > ' + data.geo.country
|
||||
?.names?.en + ' > ' + data.geo.city?.names?.en || 'Unknown';
|
||||
const reverseDns = data.reverse_dns || 'N/A';
|
||||
popup.innerHTML = `
|
||||
<strong>Location:</strong> ${location}<br>
|
||||
<strong>Reverse DNS:</strong> ${reverseDns}
|
||||
`;
|
||||
})
|
||||
.catch(() => {
|
||||
popup.innerHTML = 'Error fetching data.';
|
||||
});
|
||||
}
|
||||
|
||||
popup.style.display = 'block';
|
||||
popup.style.left = `${event.pageX + 10}px`;
|
||||
popup.style.top = `${event.pageY + 10}px`;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseout', (event) => {
|
||||
const target = event.target;
|
||||
if (target.classList.contains('ip-address')) {
|
||||
const ipAddress = target.getAttribute('data-ip');
|
||||
const popupId = `popup-${ipAddress.replace(/\./g, '-')}`;
|
||||
const popup = document.getElementById(popupId);
|
||||
if (popup) {
|
||||
popup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
|
||||
<footer class="centro-blue text-white text-center py-3">
|
||||
<div class="footer">
|
||||
<div class="container text-center centro-blue text-light">
|
||||
<h6>Copyright 2024
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body><?php }
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
<?php
|
||||
/* Smarty version 5.4.2, created on 2024-12-18 17:31:55
|
||||
from 'file:index.tpl' */
|
||||
|
||||
/* @var \Smarty\Template $_smarty_tpl */
|
||||
if ($_smarty_tpl->getCompiled()->isFresh($_smarty_tpl, array (
|
||||
'version' => '5.4.2',
|
||||
'unifunc' => 'content_6763070bd6f6f3_73946052',
|
||||
'has_nocache_code' => false,
|
||||
'file_dependency' =>
|
||||
array (
|
||||
'affb24851ed623b62affa076808377b28b01c478' =>
|
||||
array (
|
||||
0 => 'index.tpl',
|
||||
1 => 1734543111,
|
||||
2 => 'file',
|
||||
),
|
||||
),
|
||||
'includes' =>
|
||||
array (
|
||||
),
|
||||
))) {
|
||||
function content_6763070bd6f6f3_73946052 (\Smarty\Template $_smarty_tpl) {
|
||||
$_smarty_current_dir = '/home/l/public_html/xbotcontrol/smarty/template';
|
||||
?><!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>XBotControl</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous">
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.css">
|
||||
<?php echo '<script'; ?>
|
||||
src="https://code.jquery.com/jquery-3.7.1.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<!-- Latest compiled and minified Locales -->
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/locale/bootstrap-table-zh-CN.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.css">
|
||||
<?php echo '<script'; ?>
|
||||
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.js">
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main-menu " style="background-color: #003366;">
|
||||
|
||||
<div class="container text-center text-light">
|
||||
<nav class="navbar navbar-expand-lg text-light">
|
||||
<div class="container">
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('latest_requests');">Latest</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('count_requests_by_ip');">Top by
|
||||
IP</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('count_requests_by_ua');">Top by
|
||||
UA</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_ua_path');">IP+UA+Path</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_by_load');">IP+Load</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_by_rps');">IP+RPS</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_net_28_by_rps');">IP+RPS</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="content">
|
||||
<div id="main-body">
|
||||
<div class="container" style="max-width: 95%;">
|
||||
<div class="row p-3">
|
||||
<div class="col-10">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="toolbar" class="row ">
|
||||
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">Limit</div>
|
||||
|
||||
|
||||
<select id="limit" name="limit" class="form-control mr-3">
|
||||
<option value="10">10</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100" selected>100</option>
|
||||
<option value="200">200</option>
|
||||
<option value="500">500</option>
|
||||
<option value="1000">1000</option>
|
||||
<option value="100000">100000</option>
|
||||
<option value="0">0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">From</div>
|
||||
|
||||
<input type="datetime-local" id="date-from" name="date-from" class="form-control mr-3">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">To</div>
|
||||
|
||||
<input type="datetime-local" id="date-to" name="date-to" class="form-control mr-3">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<table id="table">
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php echo '<script'; ?>
|
||||
>
|
||||
document.getElementById('date-from').addEventListener('change', refreshTable);
|
||||
|
||||
document.getElementById('limit').addEventListener('change', refreshTable);
|
||||
|
||||
function refreshTable() {
|
||||
$('#table').bootstrapTable('refresh');
|
||||
}
|
||||
window.onload = function() {
|
||||
const dateFrom = document.getElementById('date-from');
|
||||
const dateTo = document.getElementById('date-to');
|
||||
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(today.getDate() - 1);
|
||||
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
|
||||
dateFrom.value = yesterday.toISOString().slice(0, 16);
|
||||
dateTo.value = tomorrow.toISOString().slice(0, 16);
|
||||
};
|
||||
document.getElementById('date-to').addEventListener('change', refreshTable);
|
||||
|
||||
|
||||
function initializeTable(latest_requests) {
|
||||
var url = location.pathname + '/api/report/' + latest_requests;
|
||||
var $table = $('#table');
|
||||
|
||||
if ($table.length) {
|
||||
$table.bootstrapTable('destroy');
|
||||
}
|
||||
|
||||
$.get(url, function(response) {
|
||||
$table.bootstrapTable({
|
||||
url: url,
|
||||
sortable: true,
|
||||
toolbar: '#toolbar',
|
||||
showRefresh: true,
|
||||
iconsPrefix: 'fa',
|
||||
showColumns: true,
|
||||
classes: ['table', 'table-borderless', 'table-hover', 'table-striped'],
|
||||
filterControl: true,
|
||||
searchable: true,
|
||||
pagination: false,
|
||||
sidePagination: "server",
|
||||
serverSort: false,
|
||||
columns: response.columns,
|
||||
queryParams: queryParams,
|
||||
loadingFontSize: '12px'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function queryParams(params) {
|
||||
const limit = document.getElementById('limit').value;
|
||||
const from = document.getElementById('date-from').value;
|
||||
const to = document.getElementById('date-to').value;
|
||||
|
||||
params.limit = limit;
|
||||
params.from = from;
|
||||
params.to = to;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function buttons() {
|
||||
<?php if ($_SESSION['user_role'] == 'admin') {?>
|
||||
return {
|
||||
btnAdd: {
|
||||
text: 'Add new list',
|
||||
icon: 'fa-plus',
|
||||
event: function() {
|
||||
// Prompt the user for a new list name
|
||||
const newListName = prompt('Enter new list name:');
|
||||
|
||||
// Only proceed if the user provides a valid list name
|
||||
if (newListName) {
|
||||
// Define the URL where the form needs to be posted
|
||||
const url = '<?php echo htmlspecialchars((string) ($_ENV['BASEURI']), ENT_QUOTES, 'UTF-8');?>
|
||||
/lists/create'; // Replace with actual URL
|
||||
|
||||
// Create a new hidden form element
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = url;
|
||||
|
||||
// Create hidden input to store the list name
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'listName'; // The name expected by the server
|
||||
input.value = newListName;
|
||||
|
||||
// Append the input to the form
|
||||
form.appendChild(input);
|
||||
|
||||
// Append the form to the body to make it part of the DOM
|
||||
document.body.appendChild(form);
|
||||
|
||||
// Submit the form automatically
|
||||
form.submit();
|
||||
} else {
|
||||
// Handle case where user cancels or enters an empty name
|
||||
alert('List creation was cancelled or name was empty.');
|
||||
}
|
||||
},
|
||||
attributes: {
|
||||
title: 'Add a new list to the table'
|
||||
}
|
||||
}
|
||||
}
|
||||
<?php } else { ?>
|
||||
return {
|
||||
|
||||
}
|
||||
|
||||
<?php }?>
|
||||
}
|
||||
|
||||
function listFormatter(value, row, index) {
|
||||
var editBtn = '<a class="btn" href="<?php echo htmlspecialchars((string) ($_ENV['BASEURI']), ENT_QUOTES, 'UTF-8');?>
|
||||
/lists/edit/' + row.list_id + '" title="Edit"><i class="fa-solid fa-pen-to-square"></i></a> ';
|
||||
|
||||
var showBtn = '<a class="btn" href="<?php echo htmlspecialchars((string) ($_ENV['BASEURI']), ENT_QUOTES, 'UTF-8');?>
|
||||
/lists/show/' + row.list_id + '" title="Show"><i class="fa-solid fa-eye"></i></a>';
|
||||
<?php if ($_SESSION['user_role'] == 'admin') {?>
|
||||
return [showBtn, editBtn, value, ].join('')
|
||||
<?php } else { ?>
|
||||
return [showBtn, value, ].join('')
|
||||
|
||||
<?php }?>
|
||||
}
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
|
||||
<?php echo '<script'; ?>
|
||||
>
|
||||
function ipFormatter(value) {
|
||||
return `<span class="ip-address" data-ip="${value}">${value}</span>`;
|
||||
}
|
||||
|
||||
document.addEventListener('mouseover', async (event) => {
|
||||
const target = event.target;
|
||||
if (target.classList.contains('ip-address')) {
|
||||
const ipAddress = target.getAttribute('data-ip');
|
||||
const popupId = `popup-${ipAddress.replace(/\./g, '-')}`;
|
||||
let popup = document.getElementById(popupId);
|
||||
|
||||
if (!popup) {
|
||||
popup = document.createElement('div');
|
||||
popup.id = popupId;
|
||||
popup.style.position = 'absolute';
|
||||
popup.style.background = '#f9f9f9';
|
||||
popup.style.border = '1px solid #ccc';
|
||||
popup.style.padding = '10px';
|
||||
popup.style.borderRadius = '5px';
|
||||
popup.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
|
||||
popup.style.zIndex = '1000';
|
||||
popup.style.whiteSpace = 'nowrap';
|
||||
popup.style.display = 'none';
|
||||
document.body.appendChild(popup);
|
||||
|
||||
fetch(location.pathname + `/api/ipinfo/${ipAddress}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const location = data.geo.continent?.names?.en + ' > ' + data.geo.country
|
||||
?.names?.en + ' > ' + data.geo.city?.names?.en || 'Unknown';
|
||||
const reverseDns = data.reverse_dns || 'N/A';
|
||||
popup.innerHTML = `
|
||||
<strong>Location:</strong> ${location}<br>
|
||||
<strong>Reverse DNS:</strong> ${reverseDns}
|
||||
`;
|
||||
})
|
||||
.catch(() => {
|
||||
popup.innerHTML = 'Error fetching data.';
|
||||
});
|
||||
}
|
||||
|
||||
popup.style.display = 'block';
|
||||
popup.style.left = `${event.pageX + 10}px`;
|
||||
popup.style.top = `${event.pageY + 10}px`;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseout', (event) => {
|
||||
const target = event.target;
|
||||
if (target.classList.contains('ip-address')) {
|
||||
const ipAddress = target.getAttribute('data-ip');
|
||||
const popupId = `popup-${ipAddress.replace(/\./g, '-')}`;
|
||||
const popup = document.getElementById(popupId);
|
||||
if (popup) {
|
||||
popup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
|
||||
<footer class="centro-blue text-white text-center py-3">
|
||||
<div class="footer">
|
||||
<div class="container text-center centro-blue text-light">
|
||||
<h6>Copyright 2024
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body><?php }
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/* Smarty version 5.4.2, created on 2024-12-18 19:19:42
|
||||
from 'file:login.tpl' */
|
||||
|
||||
/* @var \Smarty\Template $_smarty_tpl */
|
||||
if ($_smarty_tpl->getCompiled()->isFresh($_smarty_tpl, array (
|
||||
'version' => '5.4.2',
|
||||
'unifunc' => 'content_6763204e824e49_51557939',
|
||||
'has_nocache_code' => false,
|
||||
'file_dependency' =>
|
||||
array (
|
||||
'b56b63fa35b4c8d6169eae7042db3ea0125ea5bf' =>
|
||||
array (
|
||||
0 => 'login.tpl',
|
||||
1 => 1734549413,
|
||||
2 => 'file',
|
||||
),
|
||||
),
|
||||
'includes' =>
|
||||
array (
|
||||
),
|
||||
))) {
|
||||
function content_6763204e824e49_51557939 (\Smarty\Template $_smarty_tpl) {
|
||||
$_smarty_current_dir = '/home/upw/clients/kpopping/xbotcontrol/smarty/template';
|
||||
?><!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>XBotControl</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous">
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.css">
|
||||
<?php echo '<script'; ?>
|
||||
src="https://code.jquery.com/jquery-3.7.1.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<!-- Latest compiled and minified Locales -->
|
||||
<?php echo '<script'; ?>
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/locale/bootstrap-table-zh-CN.min.js"><?php echo '</script'; ?>
|
||||
>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.css">
|
||||
<?php echo '<script'; ?>
|
||||
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.js">
|
||||
<?php echo '</script'; ?>
|
||||
>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div class="content">
|
||||
|
||||
<div class="d-flex align-items-center justify-content-center vh-100">
|
||||
<form id="form" enctype="multipart/form-data" action="login" method="post" class="p-4 border rounded">
|
||||
<div class="mb-3">
|
||||
<label for="api_key" class="form-label">API Key</label>
|
||||
<input type="text" class="form-control" id="api_key" name="api_key">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success w-100">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="text-white text-center py-3">
|
||||
<div class="footer">
|
||||
<div class="container text-center text-light">
|
||||
<h6>Copyright 2024
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body><?php }
|
||||
}
|
||||
280
smarty/template/index.tpl
Normal file
280
smarty/template/index.tpl
Normal file
@@ -0,0 +1,280 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>XBotControl</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous">
|
||||
</script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.css">
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.js"></script>
|
||||
<!-- Latest compiled and minified Locales -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/locale/bootstrap-table-en-US.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.css">
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.js">
|
||||
</script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="main-menu " style="background-color: #003366;">
|
||||
|
||||
<div class="container text-center text-light">
|
||||
<nav class="navbar navbar-expand-lg text-light">
|
||||
<div class="container">
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('latest_requests');">Latest</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('count_requests_by_ip');">Top by
|
||||
IP</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('count_requests_by_ua');">Top by
|
||||
UA</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_ua_path');">IP+UA+Path</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_by_load');">IP+Load</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="btn text-light" onclick="initializeTable('top_ip_by_rps');">IP+RPS</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="content">
|
||||
<div id="main-body">
|
||||
<div class="container" style="max-width: 95%;">
|
||||
<div class="row p-3">
|
||||
<div class="col-10">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="toolbar" class="row ">
|
||||
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">Limit</div>
|
||||
|
||||
|
||||
<select id="limit" name="limit" class="form-control mr-3">
|
||||
<option value="10">10</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100" selected>100</option>
|
||||
<option value="200">200</option>
|
||||
<option value="500">500</option>
|
||||
<option value="1000">1000</option>
|
||||
<option value="100000">100000</option>
|
||||
<option value="0">0</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">From</div>
|
||||
|
||||
<input type="datetime-local" id="date-from" name="date-from" class="form-control mr-3">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text">To</div>
|
||||
|
||||
<input type="datetime-local" id="date-to" name="date-to" class="form-control mr-3">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<table id="table">
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('date-from').addEventListener('change', refreshTable);
|
||||
|
||||
document.getElementById('limit').addEventListener('change', refreshTable);
|
||||
|
||||
function refreshTable() {
|
||||
$('#table').bootstrapTable('refresh');
|
||||
}
|
||||
window.onload = function() {
|
||||
const dateFrom = document.getElementById('date-from');
|
||||
const dateTo = document.getElementById('date-to');
|
||||
|
||||
const today = new Date();
|
||||
const yesterday = new Date(today);
|
||||
yesterday.setDate(today.getDate() - 1);
|
||||
|
||||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
|
||||
dateFrom.value = yesterday.toISOString().slice(0, 16);
|
||||
dateTo.value = tomorrow.toISOString().slice(0, 16);
|
||||
};
|
||||
document.getElementById('date-to').addEventListener('change', refreshTable);
|
||||
|
||||
|
||||
function initializeTable(latest_requests) {
|
||||
var url = location.pathname + '/api/report/' + latest_requests;
|
||||
var $table = $('#table');
|
||||
|
||||
if ($table.length) {
|
||||
$table.bootstrapTable('destroy');
|
||||
}
|
||||
|
||||
$.get(url, function(response) {
|
||||
$table.bootstrapTable({
|
||||
url: url,
|
||||
sortable: true,
|
||||
toolbar: '#toolbar',
|
||||
showRefresh: true,
|
||||
iconsPrefix: 'fa',
|
||||
showColumns: true,
|
||||
classes: ['table', 'table-borderless', 'table-hover', 'table-striped'],
|
||||
filterControl: true,
|
||||
searchable: true,
|
||||
pagination: false,
|
||||
sidePagination: "server",
|
||||
serverSort: false,
|
||||
columns: response.columns,
|
||||
queryParams: queryParams,
|
||||
loadingFontSize: '12px'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function queryParams(params) {
|
||||
const limit = document.getElementById('limit').value;
|
||||
const from = document.getElementById('date-from').value;
|
||||
const to = document.getElementById('date-to').value;
|
||||
|
||||
params.limit = limit;
|
||||
params.from = from;
|
||||
params.to = to;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function listFormatter(value, row, index) {
|
||||
var editBtn = '<a class="btn" href="{$smarty.env.BASEURI}/lists/edit/' + row.list_id + '" title="Edit"><i class="fa-solid fa-pen-to-square"></i></a> ';
|
||||
|
||||
var showBtn = '<a class="btn" href="{$smarty.env.BASEURI}/lists/show/' + row.list_id + '" title="Show"><i class="fa-solid fa-eye"></i></a>';
|
||||
{if $smarty.session.user_role == 'admin'}
|
||||
return [showBtn, editBtn, value, ].join('')
|
||||
{else}
|
||||
return [showBtn, value, ].join('')
|
||||
|
||||
{/if}
|
||||
}
|
||||
</script>
|
||||
{literal}
|
||||
<script>
|
||||
function ipFormatter(value) {
|
||||
return `<span class="ip-address" data-ip="${value}">${value}</span>`;
|
||||
}
|
||||
|
||||
document.addEventListener('mouseover', async (event) => {
|
||||
const target = event.target;
|
||||
if (target.classList.contains('ip-address')) {
|
||||
const ipAddress = target.getAttribute('data-ip');
|
||||
const popupId = `popup-${ipAddress.replace(/\./g, '-')}`;
|
||||
let popup = document.getElementById(popupId);
|
||||
|
||||
if (!popup) {
|
||||
popup = document.createElement('div');
|
||||
popup.id = popupId;
|
||||
popup.style.position = 'absolute';
|
||||
popup.style.background = '#f9f9f9';
|
||||
popup.style.border = '1px solid #ccc';
|
||||
popup.style.padding = '10px';
|
||||
popup.style.borderRadius = '5px';
|
||||
popup.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.2)';
|
||||
popup.style.zIndex = '1000';
|
||||
popup.style.whiteSpace = 'nowrap';
|
||||
popup.style.display = 'none';
|
||||
document.body.appendChild(popup);
|
||||
|
||||
fetch(location.pathname + `/api/ipinfo/${ipAddress}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
const location = data.geo.continent?.names?.en + ' > ' + data.geo.country
|
||||
?.names?.en + ' > ' + data.geo.city?.names?.en || 'Unknown';
|
||||
const reverseDns = data.reverse_dns || 'N/A';
|
||||
popup.innerHTML = `
|
||||
<strong>Location:</strong> ${location}<br>
|
||||
<strong>Reverse DNS:</strong> ${reverseDns}
|
||||
`;
|
||||
})
|
||||
.catch(() => {
|
||||
popup.innerHTML = 'Error fetching data.';
|
||||
});
|
||||
}
|
||||
|
||||
popup.style.display = 'block';
|
||||
popup.style.left = `${event.pageX + 10}px`;
|
||||
popup.style.top = `${event.pageY + 10}px`;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseout', (event) => {
|
||||
const target = event.target;
|
||||
if (target.classList.contains('ip-address')) {
|
||||
const ipAddress = target.getAttribute('data-ip');
|
||||
const popupId = `popup-${ipAddress.replace(/\./g, '-')}`;
|
||||
const popup = document.getElementById(popupId);
|
||||
if (popup) {
|
||||
popup.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/literal}
|
||||
<footer class="centro-blue text-white text-center py-3">
|
||||
<div class="footer">
|
||||
<div class="container text-center centro-blue text-light">
|
||||
<h6>Copyright 2024
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
57
smarty/template/login.tpl
Normal file
57
smarty/template/login.tpl
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>XBotControl</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous">
|
||||
</script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.css">
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/bootstrap-table.min.js"></script>
|
||||
<!-- Latest compiled and minified Locales -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/locale/bootstrap-table-zh-CN.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.css">
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.5/dist/extensions/filter-control/bootstrap-table-filter-control.js">
|
||||
</script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.1/css/all.min.css" rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<div class="content">
|
||||
|
||||
<div class="d-flex align-items-center justify-content-center vh-100">
|
||||
<form id="form" enctype="multipart/form-data" action="login" method="post" class="p-4 border rounded">
|
||||
<div class="mb-3">
|
||||
<label for="api_key" class="form-label">API Key</label>
|
||||
<input type="text" class="form-control" id="api_key" name="api_key">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success w-100">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="text-white text-center py-3">
|
||||
<div class="footer">
|
||||
<div class="container text-center text-light">
|
||||
<h6>Copyright 2024
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
31
src/Bot.php
Normal file
31
src/Bot.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use Clue\React\SQLite\Result;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class Bot
|
||||
{
|
||||
|
||||
// Helper Functions
|
||||
public function isBot($userAgent): bool
|
||||
{
|
||||
$botKeywords = ['bot', 'crawl', 'spider', 'slurp', 'archive'];
|
||||
foreach ($botKeywords as $keyword) {
|
||||
if (stripos($userAgent, $keyword) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function extractBotName($userAgent): string|null
|
||||
{
|
||||
preg_match('/bot|crawl|spider|slurp|archive/i', $userAgent, $matches);
|
||||
return $matches[0] ?? null;
|
||||
}}
|
||||
25
src/Classes/GeoIp.php
Normal file
25
src/Classes/GeoIp.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Classes;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
class GeoIp
|
||||
{
|
||||
|
||||
|
||||
public static function get(string $ipAddress): PromiseInterface
|
||||
{
|
||||
return new Promise(function ($resolve) use ($ipAddress) {
|
||||
$reader = \XBotControl\Config::getinstance()->geoipreader;
|
||||
if ($reader) {
|
||||
$resolve($reader->get($ipAddress));
|
||||
} else {
|
||||
$resolve([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
273
src/Classes/IPTools.php
Normal file
273
src/Classes/IPTools.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Classes;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use Clue\React\SQLite\Result;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
class IPTools
|
||||
|
||||
{
|
||||
/**
|
||||
* Main function to check if a given IP (IPv4 or IPv6) matches a whitelist of ranges from the database.
|
||||
*
|
||||
* @param string $ip The IP address to check.
|
||||
* @param object $db The database connection object (ReactPHP-based).
|
||||
* @return \React\Promise\Promise Promise resolving to true if the IP matches any of the whitelist ranges, false otherwise.
|
||||
*/
|
||||
public static function checkIPWhitelistAsync(string $ip, $db): Promise
|
||||
{
|
||||
return self::getIPVersion($ip)->then(
|
||||
function ($ipVersion) use ($ip, $db) {
|
||||
if ($ipVersion === null) {
|
||||
return false; // Invalid IP
|
||||
}
|
||||
|
||||
return self::fetchWhitelistRanges($db, $ipVersion)->then(
|
||||
function ($ranges) use ($ip, $ipVersion) {
|
||||
if ($ipVersion === 'ipv4') {
|
||||
return self::checkIPv4($ip, $ranges);
|
||||
} elseif ($ipVersion === 'ipv6') {
|
||||
return self::checkIPv6($ip, $ranges);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the IP version (IPv4, IPv6, or null for invalid).
|
||||
*
|
||||
* @param string $ip The IP address to validate.
|
||||
* @return \React\Promise\Promise Promise resolving to 'ipv4', 'ipv6', or null.
|
||||
*/
|
||||
private static function getIPVersion(string $ip): Promise
|
||||
{
|
||||
return new Promise(function (callable $resolve) use ($ip) {
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$resolve('ipv4');
|
||||
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$resolve('ipv6');
|
||||
} else {
|
||||
$resolve(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch whitelist ranges from the database for a specific IP version.
|
||||
*
|
||||
* @param object $db The database connection object.
|
||||
* @param string $ipVersion The IP version ('ipv4' or 'ipv6').
|
||||
* @return \React\Promise\Promise Promise resolving to an array of CIDR ranges.
|
||||
*/
|
||||
private static function fetchWhitelistRanges($db, string $ipVersion): Promise
|
||||
{
|
||||
return new Promise(function (callable $resolve, callable $reject) use ($db, $ipVersion) {
|
||||
$query = 'SELECT range FROM whitelist WHERE ip_version = ?';
|
||||
|
||||
$db->query($query, [$ipVersion])->then(
|
||||
function ($result) use ($resolve) {
|
||||
$ranges = array_column($result->resultRows, 'range');
|
||||
$resolve($ranges);
|
||||
},
|
||||
function ($error) use ($reject) {
|
||||
$reject($error); // Reject if the query fails
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if a given IPv6 address is in a network from an array of ranges (asynchronous with ReactPHP Promise)
|
||||
*
|
||||
* @param string $ip IPv6 address to check
|
||||
* @param array $ranges Array of IPv6/CIDR ranges, e.g., ['2001:db8::/32', '2001:0db8:85a3::8a2e:0370:7334/128']
|
||||
* @return \React\Promise\Promise Promise resolving to true if the IPv6 is in any of the ranges, false otherwise
|
||||
*/
|
||||
public static function checkIPv6(string $ip, array $ranges): PromiseInterface
|
||||
|
||||
|
||||
{
|
||||
return new Promise(function (callable $resolve) use ($ip, $ranges) {
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($ranges as $range) {
|
||||
if (!is_string($range)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($range, '/') === false) {
|
||||
$range .= '/128';
|
||||
}
|
||||
|
||||
[$range_ip, $netmask] = explode('/', $range, 2);
|
||||
if (!filter_var($range_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || !is_numeric($netmask) || $netmask < 0 || $netmask > 128) {
|
||||
continue; // Skip invalid ranges
|
||||
}
|
||||
|
||||
$ip_bin = inet_pton($ip);
|
||||
$range_bin = inet_pton($range_ip);
|
||||
$netmask_bin = str_repeat("\xff", (int)($netmask / 8));
|
||||
|
||||
if ($netmask % 8 !== 0) {
|
||||
$netmask_bin .= chr(0xff << (8 - ($netmask % 8)));
|
||||
}
|
||||
|
||||
$netmask_bin = str_pad($netmask_bin, strlen($ip_bin), "\x00");
|
||||
|
||||
if (($ip_bin & $netmask_bin) === ($range_bin & $netmask_bin)) {
|
||||
$resolve(true); // Resolve with true if a match is found
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$resolve(false); // Resolve with false if no matches are found
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given IPv4 address is in a network from an array of ranges (asynchronous with ReactPHP Promise)
|
||||
*
|
||||
* @param string $ip IPv4 address to check
|
||||
* @param array $ranges Array of IPv4/CIDR ranges, e.g., ['192.168.1.0/24', '10.0.0.1/32']
|
||||
* @return \React\Promise\Promise Promise resolving to true if the IPv4 is in any of the ranges, false otherwise
|
||||
*/
|
||||
public static function checkIPv4(string $ip, array $ranges): Promise
|
||||
{
|
||||
|
||||
|
||||
|
||||
return new Promise(function (callable $resolve) use ($ip, $ranges) {
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($ranges as $range) {
|
||||
if (!is_string($range)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($range, '/') === false) {
|
||||
$range .= '/32';
|
||||
}
|
||||
|
||||
[$range_ip, $netmask] = explode('/', $range, 2);
|
||||
if (!filter_var($range_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) || !is_numeric($netmask) || $netmask < 0 || $netmask > 32) {
|
||||
continue; // Skip invalid ranges
|
||||
}
|
||||
|
||||
$range_decimal = ip2long($range_ip);
|
||||
$ip_decimal = ip2long($ip);
|
||||
$netmask_decimal = -1 << (32 - (int)$netmask);
|
||||
|
||||
if (($ip_decimal & $netmask_decimal) === ($range_decimal & $netmask_decimal)) {
|
||||
$resolve(true); // Resolve with true if a match is found
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$resolve(false); // Resolve with false if no matches are found
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function checkIfIpBelongsToNetwork(string $ip): PromiseInterface
|
||||
{
|
||||
return self::detectIpVersion($ip)->then(function ($ipVersion) use ($ip) {
|
||||
return self::whitelistRetrieve($ip, $ipVersion)->then(function ($networks) use ($ip, $ipVersion) {
|
||||
if ($ipVersion === 4) {
|
||||
return self::compareIpv4($ip, $networks);
|
||||
} else {
|
||||
return self::compareIpv6($ip, $networks);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static function detectIpVersion(string $ip): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$deferred->resolve(4);
|
||||
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$deferred->resolve(6);
|
||||
} else {
|
||||
$deferred->reject("Invalid IP address");
|
||||
}
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
private static function whitelistRetrieve(string $ip, int $ipVersion, string $source = '%'): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
$pdo = new \PDO("mysql:host=localhost;dbname=your_database_name", "username", "password");
|
||||
$sql = "SELECT data FROM networkwhitelist WHERE v = :version";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindParam(':version', $ipVersion, \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$networks = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
$deferred->resolve($networks);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
private static function compareIpv4(string $ip, array $networks): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
foreach ($networks as $network) {
|
||||
[$networkIp, $mask] = explode('/', $network);
|
||||
$networkLong = ip2long($networkIp);
|
||||
$maskLong = ~((1 << (32 - $mask)) - 1);
|
||||
$ipLong = ip2long($ip);
|
||||
|
||||
if (($ipLong & $maskLong) === ($networkLong & $maskLong)) {
|
||||
$deferred->resolve(true);
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
$deferred->resolve(false);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
private static function compareIpv6(string $ip, array $networks): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
foreach ($networks as $network) {
|
||||
[$networkIp, $prefixLength] = explode('/', $network);
|
||||
$networkBin = inet_pton($networkIp);
|
||||
$ipBin = inet_pton($ip);
|
||||
$prefixBytes = intval($prefixLength / 8);
|
||||
$remainingBits = $prefixLength % 8;
|
||||
|
||||
if (strncmp($networkBin, $ipBin, $prefixBytes) === 0) {
|
||||
if ($remainingBits === 0 || ord($networkBin[$prefixBytes]) >> (8 - $remainingBits) === ord($ipBin[$prefixBytes]) >> (8 - $remainingBits)) {
|
||||
$deferred->resolve(true);
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
}
|
||||
$deferred->resolve(false);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
25
src/Classes/LoadStat.php
Normal file
25
src/Classes/LoadStat.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Classes;
|
||||
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
|
||||
class LoadStat
|
||||
{
|
||||
|
||||
public static function saveLoad1()
|
||||
{
|
||||
$load = sys_getloadavg();
|
||||
if (!$load) {
|
||||
return;
|
||||
}
|
||||
$query = "INSERT OR IGNORE INTO load (load1, rowid) VALUES (?,?);";
|
||||
$params = [$load['0'], time()];
|
||||
|
||||
\XBotControl\Storage::getInstance()->db->query($query, $params);
|
||||
}
|
||||
}
|
||||
84
src/Classes/LogReader.php
Normal file
84
src/Classes/LogReader.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
|
||||
function parseLogFile(&$q, $startTime = 0)
|
||||
{
|
||||
$handle = fopen('/home/upw/clients/kpopping/kpopping_access.log', 'r');
|
||||
if ($handle) {
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = explode('; ', $line);
|
||||
|
||||
$ip = trim($parts[0]);
|
||||
$dateString = substr($parts[1], 0, 20);
|
||||
$timestamp = DateTime::createFromFormat('d/M/Y:H:i:s', $dateString)->getTimestamp();
|
||||
// var_dump( $timestamp);
|
||||
$requestParts = explode(' ', $parts[2]);
|
||||
$path = trim($requestParts[1] ?? '');
|
||||
|
||||
if ($timestamp < $startTime || strlen($ip) < 3 || str_contains($path, '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$logEntry = [
|
||||
'method' => $parts[1],
|
||||
'ip' => $ip,
|
||||
'path' => $path,
|
||||
'timestamp' => $timestamp
|
||||
];
|
||||
React\Async\await($q($logEntry));
|
||||
// process($logEntry, $counter);
|
||||
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function process($logEntry, &$counter)
|
||||
{
|
||||
$request = new React\Http\Message\ServerRequest(
|
||||
$logEntry['method'],
|
||||
'https://kpopping.com' . $logEntry['path'],
|
||||
[
|
||||
'X-Forwarded-For' => $logEntry['ip'],
|
||||
'Host' => 'kpopping.com',
|
||||
],
|
||||
'',
|
||||
'1.1'
|
||||
|
||||
);
|
||||
|
||||
return XBotControl\Request::save($request->withAttribute('original_uri', $logEntry['path']), $logEntry['timestamp']);
|
||||
}
|
||||
|
||||
|
||||
\React\Async\await(XBotControl\InitTables::create());
|
||||
|
||||
$q1 = new Clue\React\Mq\Queue(600, null, function ($logEntry) {
|
||||
$request = new React\Http\Message\ServerRequest(
|
||||
'GET',
|
||||
$logEntry['path'],
|
||||
['X-Forwarded-For' => $logEntry['ip']],
|
||||
'',
|
||||
'1.1',
|
||||
);
|
||||
return XBotControl\Request::save($request, $logEntry['timestamp']);;
|
||||
});
|
||||
$q = function ($logEntry) {
|
||||
$request = new React\Http\Message\ServerRequest(
|
||||
'GET',
|
||||
$logEntry['path'],
|
||||
['X-Forwarded-For' => $logEntry['ip']],
|
||||
'',
|
||||
'1.1',
|
||||
);
|
||||
return XBotControl\Request::save($request, $logEntry['timestamp']);;
|
||||
};
|
||||
//parseLogFile($q, 1734379562);
|
||||
363
src/Classes/Report.php
Normal file
363
src/Classes/Report.php
Normal file
@@ -0,0 +1,363 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Classes;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class Report
|
||||
{
|
||||
private static function generateColumns(array $definitions): array
|
||||
{
|
||||
$columns = [];
|
||||
foreach ($definitions as $definition) {
|
||||
$columns[] = array_merge(
|
||||
[
|
||||
'sortable' => true,
|
||||
'visible' => true,
|
||||
'filterControl' => 'input',
|
||||
],
|
||||
$definition
|
||||
);
|
||||
}
|
||||
return $columns;
|
||||
}
|
||||
|
||||
private static function executeQuery(string $sql, array $params, array $columnsDefinition): PromiseInterface
|
||||
{
|
||||
return \XBotControl\Storage::getInstance()->db->query($sql, $params)->then(function ($result) use ($columnsDefinition) {
|
||||
return [
|
||||
"columns" => $columnsDefinition,
|
||||
"rows" => $result->rows,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
private static function parseQueryParams(ServerRequestInterface $request): array
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
return [
|
||||
'from' => strtotime($query['from'] ?? 'yesterday'),
|
||||
'to' => strtotime($query['to'] ?? 'now'),
|
||||
'limit' => (int)($query['limit'] ?? 100),
|
||||
'filter' => isset($query['filter']) ? json_decode($query['filter'], true) : []
|
||||
];
|
||||
}
|
||||
|
||||
private static function prepareFilterClauses(array $filter): array
|
||||
{
|
||||
$sql = '';
|
||||
$params = [];
|
||||
foreach ($filter as $field => $value) {
|
||||
$sql .= 'AND ' . $field . ' LIKE ? ';
|
||||
$params[] = '%' . $value . '%';
|
||||
}
|
||||
return [$sql, $params];
|
||||
}
|
||||
|
||||
public static function latest_requests(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "id", "field" => "id", "visible" => false],
|
||||
["title" => "ip", "field" => "ip", 'formatter' => 'ipFormatter'],
|
||||
["title" => "domain", "field" => "domain", "visible" => false],
|
||||
["title" => "path", "field" => "path"],
|
||||
["title" => "useragent", "field" => "useragent"],
|
||||
["title" => "load", "field" => "load"],
|
||||
["title" => "datetime", "field" => "datetime"],
|
||||
]);
|
||||
|
||||
$queryParams = self::parseQueryParams($request);
|
||||
if (!isset($queryParams['limit'])) {
|
||||
return [
|
||||
"columns" => $columnsDefinition,
|
||||
"rows" => [],
|
||||
];
|
||||
}
|
||||
$sql = "
|
||||
SELECT
|
||||
req.rowid AS id, ip.data AS ip, domain.data AS domain,
|
||||
path.data AS path, useragent.data AS useragent,
|
||||
headers.data AS headers,
|
||||
(SELECT load.load1 FROM load WHERE load.rowid >= req.timestamp ORDER BY load.rowid DESC LIMIT 1) AS load,
|
||||
datetime(req.timestamp, 'auto') AS datetime
|
||||
FROM
|
||||
request req
|
||||
LEFT JOIN ip ON req.id_ip = ip.rowid
|
||||
LEFT JOIN domain ON req.id_domain = domain.rowid
|
||||
LEFT JOIN path ON req.id_path = path.rowid
|
||||
LEFT JOIN useragent ON req.id_useragent = useragent.rowid
|
||||
LEFT JOIN headers ON req.id_headers = headers.rowid
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
|
||||
$sql .= $filterSQL . " AND req.timestamp BETWEEN ? AND ? ORDER BY req.rowid DESC LIMIT ?;";
|
||||
$params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
|
||||
|
||||
return self::executeQuery($sql, $params, $columnsDefinition);
|
||||
}
|
||||
|
||||
public static function count_requests_by_ip(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "ip_address", 'formatter' => 'ipFormatter'],
|
||||
["title" => "request_count", "field" => "request_count"],
|
||||
]);
|
||||
|
||||
$queryParams = self::parseQueryParams($request);
|
||||
if (!isset($queryParams['limit'])) {
|
||||
return [
|
||||
"columns" => $columnsDefinition,
|
||||
"rows" => [],
|
||||
];
|
||||
}
|
||||
$sql = "
|
||||
SELECT
|
||||
ip.data AS ip_address,
|
||||
COUNT(request.id_ip) AS request_count
|
||||
FROM
|
||||
request
|
||||
INNER JOIN
|
||||
ip ON request.id_ip = ip.rowid
|
||||
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
|
||||
$sql .= $filterSQL . " AND request.timestamp BETWEEN ? AND ? GROUP BY
|
||||
ip.data ORDER BY request_count DESC LIMIT ?;";
|
||||
$params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
|
||||
|
||||
return self::executeQuery($sql, $params, $columnsDefinition);
|
||||
}
|
||||
|
||||
public static function count_requests_by_ua(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "useragent", "field" => "id_useragent"],
|
||||
["title" => "request_count", "field" => "request_count"],
|
||||
]);
|
||||
|
||||
$queryParams = self::parseQueryParams($request);
|
||||
if (!isset($queryParams['limit'])) {
|
||||
return [
|
||||
"columns" => $columnsDefinition,
|
||||
"rows" => [],
|
||||
];
|
||||
}
|
||||
$sql = "
|
||||
SELECT
|
||||
useragent.data AS id_useragent,
|
||||
COUNT(request.id_useragent) AS request_count
|
||||
FROM
|
||||
request
|
||||
INNER JOIN
|
||||
useragent ON request.id_useragent = useragent.rowid
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
|
||||
$sql .= $filterSQL . " AND req.timestamp BETWEEN ? AND ? GROUP BY useragent.data ORDER BY request_count DESC LIMIT ?;";
|
||||
$params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
|
||||
|
||||
return self::executeQuery($sql, $params, $columnsDefinition);
|
||||
}
|
||||
|
||||
public static function top_ip_ua_path(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "ip", 'formatter' => 'ipFormatter'],
|
||||
["title" => "useragent", "field" => "user_agent"],
|
||||
["title" => "path", "field" => "path"],
|
||||
["title" => "count", "field" => "count"],
|
||||
]);
|
||||
|
||||
$queryParams = self::parseQueryParams($request);
|
||||
if (!isset($queryParams['limit'])) {
|
||||
return [
|
||||
"columns" => $columnsDefinition,
|
||||
"rows" => [],
|
||||
];
|
||||
}
|
||||
$sql = "
|
||||
SELECT
|
||||
ip.data AS ip,
|
||||
useragent.data AS user_agent,
|
||||
path.data AS path,
|
||||
COUNT(request.rowid) AS count
|
||||
FROM
|
||||
request
|
||||
JOIN ip ON request.id_ip = ip.rowid
|
||||
JOIN useragent ON request.id_useragent = useragent.rowid
|
||||
JOIN path ON request.id_path = path.rowid
|
||||
|
||||
WHERE 1=1
|
||||
";
|
||||
|
||||
list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
|
||||
$sql .= $filterSQL . " AND request.timestamp BETWEEN ? AND ? GROUP BY ip.data, useragent.data, path.data ORDER BY count DESC LIMIT ?;";
|
||||
|
||||
$params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
|
||||
|
||||
return self::executeQuery($sql, $params, $columnsDefinition);
|
||||
}
|
||||
|
||||
public static function top_ip_by_load(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "data", 'formatter' => 'ipFormatter'],
|
||||
["title" => "avg_load", "field" => "avg_load"],
|
||||
["title" => "request_count", "field" => "request_count"],
|
||||
]);
|
||||
|
||||
$queryParams = self::parseQueryParams($request);
|
||||
if (!isset($queryParams['limit'])) {
|
||||
return [
|
||||
"columns" => $columnsDefinition,
|
||||
"rows" => [],
|
||||
];
|
||||
}
|
||||
$sql = "
|
||||
SELECT
|
||||
ip.data,
|
||||
COUNT(request.rowid) AS request_count,
|
||||
AVG(load.load1) AS avg_load
|
||||
FROM
|
||||
request
|
||||
JOIN ip ON request.id_ip = ip.rowid
|
||||
JOIN load ON load.rowid = (
|
||||
SELECT MIN(load_sub.rowid)
|
||||
FROM load AS load_sub
|
||||
WHERE load_sub.rowid > request.timestamp
|
||||
)
|
||||
WHERE load.load1 > 1
|
||||
";
|
||||
|
||||
list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
|
||||
$sql .= $filterSQL . " AND request.timestamp BETWEEN ? AND ? GROUP BY ip.data ORDER BY avg_load DESC LIMIT ?;";
|
||||
|
||||
$params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
|
||||
|
||||
return self::executeQuery($sql, $params, $columnsDefinition);
|
||||
}
|
||||
|
||||
public static function top_ip_by_rps(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "ip_address", 'formatter' => 'ipFormatter'],
|
||||
["title" => "avg_request_per_second", "field" => "avg_request_per_second"],
|
||||
]);
|
||||
|
||||
$queryParams = self::parseQueryParams($request);
|
||||
if (!isset($queryParams['limit'])) {
|
||||
return [
|
||||
"columns" => $columnsDefinition,
|
||||
"rows" => [],
|
||||
];
|
||||
}
|
||||
$sql = "
|
||||
WITH TimestampIPRequests AS (
|
||||
SELECT
|
||||
id_ip,
|
||||
timestamp,
|
||||
COUNT(*) AS request_count
|
||||
FROM
|
||||
request
|
||||
WHERE
|
||||
|
||||
request.timestamp BETWEEN ? AND ?
|
||||
GROUP BY
|
||||
id_ip, timestamp
|
||||
HAVING
|
||||
COUNT(*) > 1
|
||||
),
|
||||
IPRequestPerSecond AS (
|
||||
SELECT
|
||||
id_ip,
|
||||
AVG(request_count) AS avg_request_per_second
|
||||
FROM
|
||||
TimestampIPRequests
|
||||
|
||||
GROUP BY
|
||||
id_ip
|
||||
)
|
||||
SELECT
|
||||
ip.data as ip_address,
|
||||
avg_request_per_second
|
||||
FROM
|
||||
IPRequestPerSecond
|
||||
JOIN ip ON IPRequestPerSecond.id_ip = ip.rowid
|
||||
|
||||
WHERE 1 = 1
|
||||
";
|
||||
|
||||
list($filterSQL, $filterParams) = self::prepareFilterClauses($queryParams['filter']);
|
||||
$sql .= $filterSQL . " GROUP BY ip.data ORDER BY avg_request_per_second DESC LIMIT ?;";
|
||||
$params = array_merge($filterParams, [$queryParams['from'], $queryParams['to'], $queryParams['limit']]);
|
||||
|
||||
return self::executeQuery($sql, $params, $columnsDefinition);
|
||||
}
|
||||
|
||||
public static function top_net_28_by_rps(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "network"],
|
||||
["title" => "avg_request_per_second", "field" => "avg_request_per_second"],
|
||||
]);
|
||||
|
||||
$queryParams = self::parseQueryParams($request);
|
||||
if (!isset($queryParams['limit'])) {
|
||||
return [
|
||||
"columns" => $columnsDefinition,
|
||||
"rows" => [],
|
||||
];
|
||||
}
|
||||
$sql = "
|
||||
CREATE FUNCTION cidr_to_network(cidr VARCHAR(30), prefix INT) RETURNS VARCHAR(30)
|
||||
BEGIN
|
||||
RETURN inet_ntoa(inet_aton(substring_index(cidr, '/', 1)) & ((2 ^ (32 - prefix)) - 1 ^ 0xFFFFFFFF)) || '/' || prefix;
|
||||
END;
|
||||
|
||||
WITH TimestampNetworkRequests AS (
|
||||
SELECT
|
||||
CAST(cidr_to_network(ip.data, 28) AS TEXT) AS network,
|
||||
timestamp,
|
||||
COUNT(*) AS request_count
|
||||
FROM
|
||||
request
|
||||
JOIN
|
||||
ip ON request.id_ip = ip.rowid
|
||||
WHERE
|
||||
request.timestamp BETWEEN ? AND ?
|
||||
GROUP BY
|
||||
network, timestamp
|
||||
HAVING
|
||||
COUNT(*) > 1
|
||||
),
|
||||
NetworkRequestPerSecond AS (
|
||||
SELECT
|
||||
network,
|
||||
AVG(request_count) AS avg_request_per_second
|
||||
FROM
|
||||
TimestampNetworkRequests
|
||||
GROUP BY
|
||||
network
|
||||
)
|
||||
SELECT
|
||||
network AS network_address,
|
||||
avg_request_per_second
|
||||
FROM
|
||||
NetworkRequestPerSecond
|
||||
ORDER BY
|
||||
avg_request_per_second DESC
|
||||
LIMIT ?;
|
||||
";
|
||||
|
||||
$params = [$queryParams['from'], $queryParams['to'], $queryParams['limit']];
|
||||
|
||||
return self::executeQuery($sql, $params, $columnsDefinition);
|
||||
}
|
||||
}
|
||||
33
src/Classes/ReverseDNS.php
Normal file
33
src/Classes/ReverseDNS.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Classes;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class ReverseDNS
|
||||
{
|
||||
|
||||
public static function resolve($ip): PromiseInterface
|
||||
{
|
||||
$reverseName = self::getReverseName($ip);
|
||||
return \XBotControl\Config::getInstance()->dnsResolver->resolveAll($reverseName, \React\Dns\Model\Message::TYPE_PTR)->then(function ($ips) {
|
||||
return $ips;
|
||||
}, function () use ($ip){
|
||||
return $ip;
|
||||
});
|
||||
}
|
||||
|
||||
private static function getReverseName($ip)
|
||||
{
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa';
|
||||
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$unpackedIp = unpack('H*', inet_pton($ip))[1];
|
||||
$reverseIp = implode('.', array_reverse(str_split($unpackedIp))) . '.ip6.arpa';
|
||||
return $reverseIp;
|
||||
} else {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/Classes/Schedule.php
Normal file
62
src/Classes/Schedule.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Classes;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
|
||||
/**
|
||||
* Class Schedule
|
||||
*
|
||||
* Manages the scheduling and execution of periodic tasks, ensuring tasks with the same interval
|
||||
* are distributed to avoid simultaneous execution.
|
||||
*/
|
||||
class Schedule
|
||||
{
|
||||
/**
|
||||
* A predefined schedule of tasks to be executed.
|
||||
*
|
||||
* @const array SCHEDULE
|
||||
* - Each item is an array with:
|
||||
* - 'interval': Interval time in seconds.
|
||||
* - 'task': The callable to execute.
|
||||
*/
|
||||
private const SCHEDULE = [
|
||||
['interval' => 60, 'task' => [\XBotControl\Classes\LoadStat::class, 'saveLoad1']],
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* Initializes and runs the task scheduler.
|
||||
*
|
||||
* Loops through the SCHEDULE array, setting up periodic timers
|
||||
* using React's event loop. If multiple tasks share the same interval,
|
||||
* they are distributed by adding evenly spaced offsets to avoid collisions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function run(): void
|
||||
{
|
||||
$tasksByInterval = [];
|
||||
|
||||
// Group tasks by interval
|
||||
foreach (self::SCHEDULE as $schedule) {
|
||||
$interval = $schedule['interval'];
|
||||
$tasksByInterval[$interval][] = $schedule['task'];
|
||||
}
|
||||
|
||||
// Schedule tasks for each interval
|
||||
foreach ($tasksByInterval as $interval => $tasks) {
|
||||
$taskCount = count($tasks);
|
||||
foreach ($tasks as $index => $task) {
|
||||
// Distribute tasks evenly within the interval
|
||||
$offset = ($index / $taskCount) * $interval;
|
||||
|
||||
Loop::addTimer($offset, function () use ($interval, $task) {
|
||||
Loop::addPeriodicTimer($interval, fn() => call_user_func($task));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/Config.php
Normal file
85
src/Config.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl;
|
||||
|
||||
class Config
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Config|null
|
||||
*/
|
||||
protected static $instance;
|
||||
public $db;
|
||||
public $smarty;
|
||||
public $geoipreader;
|
||||
public $dnsResolver;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
|
||||
$this->smarty = new \Smarty\Smarty();
|
||||
$this->smarty->setTemplateDir(__DIR__ . '/../smarty/template/');
|
||||
$this->smarty->setConfigDir(__DIR__ . '/../smarty/config/');
|
||||
$this->smarty->setCompileDir(__DIR__ . '/../smarty/compile/');
|
||||
$this->smarty->setCacheDir(__DIR__ . '/../smarty/cache/');
|
||||
$this->smarty->setEscapeHtml(true);
|
||||
$this->smarty->assign([
|
||||
'baseURI' => $_ENV['BASEURI'],
|
||||
]);
|
||||
$this->smarty->compile_check = 1;
|
||||
if (isset($_ENV['GEOIP_DB_FILE_PATH'])) {
|
||||
$this->geoipreader = new \MaxMind\Db\Reader($_ENV['APP_DIR'].'/'.$_ENV['GEOIP_DB_FILE']);
|
||||
}
|
||||
$dnsConfig = \React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
$dnsConfig->nameservers[] = '8.8.8.8';
|
||||
$dnsConfig->nameservers[] = '8.8.4.4';
|
||||
$this->dnsResolver = (new \React\Dns\Resolver\Factory())->createCached($dnsConfig);
|
||||
|
||||
/* $this->db = (new \Clue\React\SQLite\Factory())->openLazy($_ENV['APP_DIR'] . '/bots.db');
|
||||
'uaCache' => new React\Cache\ArrayCache(1000),
|
||||
'ipCache' => new React\Cache\ArrayCache(1000),
|
||||
'headerCache' => new React\Cache\ArrayCache(1000),
|
||||
'domainCache' => new React\Cache\ArrayCache(1000),
|
||||
'pathCache' => new React\Cache\ArrayCache(1000), */
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if (empty(self::$instance)) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public static function registerAssetRoutes(&$app)
|
||||
{
|
||||
// Define the directory to scan
|
||||
$assetsDir = realpath(__DIR__ . '/../public/assets');
|
||||
|
||||
if ($assetsDir === false) {
|
||||
throw new \Exception('Assets directory not found');
|
||||
}
|
||||
|
||||
// Create a recursive directory iterator to scan all files
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($assetsDir)
|
||||
);
|
||||
|
||||
// Iterate through all files in the assets directory
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile()) {
|
||||
|
||||
// Get the relative path of the file
|
||||
$relativePath = str_replace($assetsDir, '', $file->getRealPath());
|
||||
|
||||
// Register the route
|
||||
$route = $_ENV['BASEURI'] . '/assets' . str_replace('\\', '/', $relativePath);
|
||||
|
||||
$app->get($route, Controllers\StaticFilesController::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/Controllers/APIController.php
Normal file
40
src/Controllers/APIController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Controllers;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class APIController
|
||||
{
|
||||
|
||||
|
||||
public function __invoke(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
|
||||
switch ($request->getAttribute('action')) {
|
||||
case 'report':
|
||||
return call_user_func([\XBotControl\Classes\Report::class, $request->getAttribute('resource')], $request)->then(function ($result) {
|
||||
return \React\Http\Message\Response::json($result);
|
||||
});
|
||||
case 'ipinfo':
|
||||
$ipAddress = $request->getAttribute('resource');
|
||||
return call_user_func([\XBotControl\Classes\GeoIp::class, 'get'], $ipAddress)
|
||||
->then(function ($geoResult) use ($ipAddress) {
|
||||
return \XBotControl\Classes\ReverseDNS::resolve($ipAddress) // Replace SomeClass with the correct class name
|
||||
->then(function ($reverseDns) use ($geoResult) {
|
||||
return \React\Http\Message\Response::json([
|
||||
'geo' => $geoResult,
|
||||
'reverse_dns' => $reverseDns
|
||||
]);
|
||||
});
|
||||
});
|
||||
default:
|
||||
return \React\Http\Message\Response::json(
|
||||
['empty_response']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/Controllers/AuthController.php
Normal file
30
src/Controllers/AuthController.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Controllers;
|
||||
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
|
||||
|
||||
class AuthController
|
||||
{
|
||||
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, callable $next)
|
||||
{
|
||||
|
||||
|
||||
if (isset($_SESSION['API_KEY']) && $_SESSION['API_KEY'] === $_ENV['API_KEY']) {
|
||||
return $next($request);
|
||||
}
|
||||
return new Response(
|
||||
Response::STATUS_FOUND,
|
||||
[
|
||||
'Location' => $_ENV['BASE_URI'] . '/login'
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
25
src/Controllers/IndexController.php
Normal file
25
src/Controllers/IndexController.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Controllers;
|
||||
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class IndexController
|
||||
{
|
||||
|
||||
|
||||
public function __invoke(ServerRequestInterface $request): \React\Http\Message\Response
|
||||
{
|
||||
$smarty = \XBotControl\Config::getInstance()->smarty;
|
||||
$smarty->assign([
|
||||
|
||||
]);
|
||||
|
||||
return \React\Http\Message\Response::html(
|
||||
$smarty->fetch('index.tpl')
|
||||
);
|
||||
}
|
||||
}
|
||||
34
src/Controllers/LoginController.php
Normal file
34
src/Controllers/LoginController.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Controllers;
|
||||
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
|
||||
class LoginController
|
||||
{
|
||||
|
||||
|
||||
public function __invoke(ServerRequestInterface $request): \React\Http\Message\Response
|
||||
{
|
||||
|
||||
$data = $request->getParsedBody();
|
||||
if ($data['api_key'] === $_ENV['API_KEY']) {
|
||||
$_SESSION['API_KEY'] = $_ENV['API_KEY'];
|
||||
$uri = $request->getUri();
|
||||
var_dump($uri->getPath() );
|
||||
return new Response(
|
||||
Response::STATUS_FOUND,
|
||||
[
|
||||
'Location' => $_ENV['BASE_URI'] . '/'
|
||||
]
|
||||
);
|
||||
}
|
||||
return Response::html(
|
||||
\XBotControl\Config::getInstance()->smarty->fetch('login.tpl')
|
||||
);
|
||||
}
|
||||
}
|
||||
63
src/Controllers/StaticFilesController.php
Normal file
63
src/Controllers/StaticFilesController.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Controllers;
|
||||
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class StaticFilesController
|
||||
{
|
||||
|
||||
/**
|
||||
* Mapping between file extension and MIME type to send in `Content-Type` response header
|
||||
*
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private $mimetypes = array(
|
||||
'atom' => 'application/atom+xml',
|
||||
'bz2' => 'application/x-bzip2',
|
||||
'css' => 'text/css',
|
||||
'gif' => 'image/gif',
|
||||
'gz' => 'application/gzip',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'ico' => 'image/x-icon',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'jpg' => 'image/jpeg',
|
||||
'js' => 'text/javascript',
|
||||
'json' => 'application/json',
|
||||
'pdf' => 'application/pdf',
|
||||
'png' => 'image/png',
|
||||
'rss' => 'application/rss+xml',
|
||||
'svg' => 'image/svg+xml',
|
||||
'tar' => 'application/x-tar',
|
||||
'xml' => 'application/xml',
|
||||
'zip' => 'application/zip',
|
||||
);
|
||||
public function __invoke(ServerRequestInterface $request)
|
||||
{
|
||||
|
||||
|
||||
$path = $request->getUri()->getPath();
|
||||
$cleanedPath = $_ENV['APP_DIR'] . '/public' . str_replace($_ENV['BASEURI'], '', $path);
|
||||
|
||||
$stream = new \React\Stream\ReadableResourceStream(fopen($cleanedPath, 'r'), null, 65536);
|
||||
$ext = strtolower(substr($path, strrpos($path, '.') + 1));
|
||||
|
||||
return new \React\Http\Message\Response(
|
||||
\React\Http\Message\Response::STATUS_OK,
|
||||
[
|
||||
'Content-Type' => $this->mimetypes[$ext] ?? 'text/html',
|
||||
'Cache-Control' => 'max-age=15552000',
|
||||
'Content-length' => filesize($cleanedPath),
|
||||
'Expires' => gmdate('D, d M Y H:i:s T', strtotime('next month')),
|
||||
'Date' => gmdate('D, d M Y H:i:s T', filemtime($cleanedPath)),
|
||||
'Last-modified' => gmdate('D, d M Y H:i:s T',filectime($cleanedPath))
|
||||
|
||||
],
|
||||
$stream
|
||||
);
|
||||
}
|
||||
}
|
||||
40
src/InitTables.php
Normal file
40
src/InitTables.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class InitTables
|
||||
{
|
||||
|
||||
public static function create():PromiseInterface
|
||||
{
|
||||
|
||||
$db = Storage::getInstance()->db;
|
||||
return $db->exec("CREATE TABLE IF NOT EXISTS ip (data TEXT UNIQUE NOT NULL CHECK (data LIKE '%'), CONSTRAINT valid_ip CHECK (data LIKE '%.%' OR data LIKE '%:%')) STRICT ;")
|
||||
->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS domain (data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS path (data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS useragent ( data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS headers ( data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec("CREATE TABLE IF NOT EXISTS networkwhitelist ( data TEXT UNIQUE NOT NULL CHECK (data LIKE '%/%'), source TEXT NULL , v INT NOT NULL , CONSTRAINT valid_network CHECK ( data LIKE '%.%/%' OR data LIKE '%:%/%' )) STRICT ;");
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS request ( id_ip INTEGER NOT NULL, id_method INTEGER NOT NULL, id_domain INTEGER NOT NULL, id_path INTEGER NOT NULL, id_useragent INTEGER NOT NULL, id_headers INTEGER NOT NULL, timestamp INTEGER NOT NULL, FOREIGN KEY (id_ip) REFERENCES ip(rowid), FOREIGN KEY (id_domain) REFERENCES domain(rowid), FOREIGN KEY (id_path) REFERENCES path(rowid), FOREIGN KEY (id_useragent) REFERENCES useragent(rowid), FOREIGN KEY (id_headers) REFERENCES headers(rowid) ) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS bot ( name TEXT NOT NULL, keyword TEXT NULL ) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS settings ( key TEXT UNIQUE NOT NULL, value TEXT NULL ) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS load (load1 REAL NOT NULL) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('PRAGMA journal_mode=WAL;');
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
33
src/Instances/Whitelist.php
Normal file
33
src/Instances/Whitelist.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl;
|
||||
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use Clue\React\SQLite\Result;
|
||||
|
||||
class Whitelist
|
||||
{
|
||||
private static ?Whitelist $instance = null;
|
||||
|
||||
public $googleUrls = [
|
||||
'https://developers.google.com/static/search/apis/ipranges/googlebot.json',
|
||||
'https://developers.google.com/static/search/apis/ipranges/special-crawlers.json',
|
||||
'https://developers.google.com/static/search/apis/ipranges/user-triggered-fetchers.json',
|
||||
'https://developers.google.com/static/search/apis/ipranges/user-triggered-fetchers-google.json'
|
||||
];
|
||||
|
||||
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function getInstance(): Whitelist
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
88
src/Request.php
Normal file
88
src/Request.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use Clue\React\SQLite\Result;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Promise;
|
||||
use function React\Promise\Timer\sleep;
|
||||
|
||||
class Request
|
||||
|
||||
{
|
||||
|
||||
const METHOD = [
|
||||
'GET' => 1,
|
||||
'HEAD' => 2,
|
||||
'OPTIONS' => 3,
|
||||
'TRACE' => 4,
|
||||
'PUT' => 5,
|
||||
'DELETE' => 6,
|
||||
'POST' => 7,
|
||||
'PATCH' => 8,
|
||||
'CONNECT' => 9
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
public static function save(ServerRequestInterface $request, ?int $timestampOverride = null): PromiseInterface
|
||||
{
|
||||
$realIp = self::getRealIP($request);
|
||||
$userAgent = $request->getHeaderLine('User-Agent') ?: 'Unknown';
|
||||
$headers = json_encode($request->getHeaders(), JSON_UNESCAPED_UNICODE);
|
||||
$uri = $request->getUri();
|
||||
$storage = Storage::getInstance();
|
||||
|
||||
// Use parallel promises for ID generation to avoid waiting for each in sequence
|
||||
$idPromises = [
|
||||
'id_ip' => $storage::getId('ip', $realIp),
|
||||
'id_domain' => $storage::getId('domain', $uri->getHost()),
|
||||
'id_path' => $storage::getId('path', '/' . $uri->getPath()),
|
||||
'id_useragent' => $storage::getId('useragent', $userAgent),
|
||||
'id_headers' => 0,
|
||||
'id_method' => self::METHOD[$request->getMethod()] ?? 0,
|
||||
'timestamp' => $timestampOverride ?? time(),
|
||||
];
|
||||
|
||||
if ($_ENV['SAVE_HEADERS'] === true) {
|
||||
$idPromises['id_headers'] = $storage::getId('headers', $headers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return \React\Promise\all($idPromises)
|
||||
->then(function ($resolvedValues) use ($request, $storage) {
|
||||
// Set resolved values efficiently
|
||||
// $resolvedValues['id_method'] = self::METHOD[$request->getMethod()] ?? 0;
|
||||
// $resolvedValues['timestamp'] = time();
|
||||
|
||||
// Directly save data asynchronously
|
||||
return $storage::insert('request', $resolvedValues);
|
||||
})
|
||||
->then(function () {
|
||||
return \React\Http\Message\Response::plaintext('');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public static function getRealIP(ServerRequestInterface $request): string
|
||||
{
|
||||
$cfConnectingIp = $request->getHeaderLine('CF-Connecting-IP');
|
||||
if (!empty($cfConnectingIp)) {
|
||||
return $cfConnectingIp;
|
||||
}
|
||||
|
||||
$xForwardedFor = $request->getHeaderLine('X-Forwarded-For');
|
||||
if (!empty($xForwardedFor)) {
|
||||
return explode(',', $xForwardedFor)[0];
|
||||
}
|
||||
|
||||
$remoteAddr = $request->getServerParams()['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
return $remoteAddr;
|
||||
}
|
||||
}
|
||||
102
src/Storage.php
Normal file
102
src/Storage.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl;
|
||||
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use Clue\React\SQLite\Result;
|
||||
|
||||
class Storage
|
||||
{
|
||||
private static ?Storage $instance = null;
|
||||
|
||||
/** @var DatabaseInterface $db */
|
||||
|
||||
public $db;
|
||||
public $cache = [];
|
||||
|
||||
protected static $tablesCache = [
|
||||
'ip',
|
||||
'domain',
|
||||
'useragent',
|
||||
'headers',
|
||||
'path'
|
||||
];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
$this->db = (new \Clue\React\SQLite\Factory())->openLazy($_ENV['APP_DIR'] . '/requests.sqlite3');
|
||||
|
||||
foreach (self::$tablesCache as $cacheParition) {
|
||||
$this->cache[$cacheParition] = new \React\Cache\ArrayCache(1000);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getInstance(): Storage
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public static function getId(string $cacheParition, string $key): PromiseInterface
|
||||
{
|
||||
$storage = self::getInstance();
|
||||
|
||||
/* if (!isset(self::$tablesCache[$cacheParition])) {
|
||||
return $storage::insert($cacheParition, ['data' => $key])->then(function ($result) {
|
||||
return $result->rows["0"][$result->columns['0']];
|
||||
});
|
||||
} */
|
||||
|
||||
|
||||
return $storage->cache[$cacheParition]->get($key)
|
||||
->then(function ($result) use ($storage, $cacheParition, $key) {
|
||||
if ($result === null) {
|
||||
return self::insertAndCache($storage, $cacheParition, $key);
|
||||
}
|
||||
return (int) $result;
|
||||
}, function () {
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static function insertAndCache(Storage $storage, string $cacheParition, string $key): PromiseInterface
|
||||
{
|
||||
|
||||
$query = "INSERT INTO $cacheParition (data) VALUES (?) ON CONFLICT(data) DO UPDATE SET data=? RETURNING rowid ;";
|
||||
|
||||
return $storage->db
|
||||
->query($query, [$key, $key])
|
||||
->then(function (Result $result) use ($storage, $cacheParition, $key) {
|
||||
|
||||
return self::cache($storage, $cacheParition, $key, $result);
|
||||
});
|
||||
}
|
||||
|
||||
private static function cache(Storage $storage, string $cacheParition, string $key, Result $result): PromiseInterface
|
||||
{
|
||||
$value = $result->rows["0"][$result->columns['0']];
|
||||
return $storage->cache[$cacheParition]->set($key, $value)
|
||||
->then(function () use ($value) {
|
||||
return $value;
|
||||
});
|
||||
}
|
||||
|
||||
public static function insert(string $table, array $values): PromiseInterface
|
||||
{
|
||||
$columns = implode(", ", array_keys($values));
|
||||
$placeholders = implode(", ", array_fill(0, count($values), "?"));
|
||||
$query = sprintf("INSERT INTO %s (%s) VALUES (%s);", $table, $columns, $placeholders);
|
||||
$params = array_values($values);
|
||||
|
||||
$storage = self::getInstance();
|
||||
return $storage->db->query($query, $params)->then(function (Result $result) {
|
||||
return $result->insertId;
|
||||
});
|
||||
}
|
||||
}
|
||||
2
vendor/clue/framework-x/.github/FUNDING.yml
vendored
2
vendor/clue/framework-x/.github/FUNDING.yml
vendored
@@ -1,2 +0,0 @@
|
||||
github: clue
|
||||
custom: https://clue.engineering/support
|
||||
373
vendor/clue/framework-x/CHANGELOG.md
vendored
373
vendor/clue/framework-x/CHANGELOG.md
vendored
@@ -1,373 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## 0.16.0 (2024-03-05)
|
||||
|
||||
We are thrilled to announce the official release of `v0.16.0` to the public! 🎉🚀
|
||||
Additionally, we are making all previous tagged versions available to simplify the upgrade process.
|
||||
In addition to the release of `v0.16.0`, this update includes all prior tagged releases.
|
||||
|
||||
This release includes exciting new features such as improved performance, additional options
|
||||
for access logging, updates to our documentation and nginx + Apache configurations,
|
||||
as well as many more internal improvements to our test suite and integration tests.
|
||||
|
||||
* Feature: Improve performance by skipping `AccessLogHandler` if it writes to `/dev/null`.
|
||||
(#248 by @clue)
|
||||
|
||||
* Feature: Add optional `$path` argument for `AccessLogHandler`.
|
||||
(#247 by @clue)
|
||||
|
||||
* Minor documentation improvements and update nginx + Apache configuration.
|
||||
(#245 and #251 by @clue)
|
||||
|
||||
* Improve test suite with improved directory structure for integration tests.
|
||||
(#250 by @clue)
|
||||
|
||||
## 0.15.0 (2023-12-07)
|
||||
|
||||
* Feature: Full PHP 8.3 compatibility.
|
||||
(#244 by @clue)
|
||||
|
||||
* Feature: Add `App::__invoke()` method to enable custom integrations.
|
||||
(#236 by @clue)
|
||||
|
||||
* Feature: Improve performance by only using `FiberHandler` for `ReactiveHandler`.
|
||||
(#237 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#242 by @yadaiio)
|
||||
|
||||
## 0.14.0 (2023-07-31)
|
||||
|
||||
* Feature: Improve Promise v3 support and use Promise v3 template types.
|
||||
(#233 and #235 by @clue)
|
||||
|
||||
* Feature: Improve handling `OPTIONS *` requests.
|
||||
(#226 by @clue)
|
||||
|
||||
* Refactor logging into new `LogStreamHandler` and reactive server logic into new `ReactiveHandler`.
|
||||
(#222 and #224 by @clue)
|
||||
|
||||
* Improve test suite and ensure 100% code coverage.
|
||||
(#217, #221, #225 and #228 by @clue)
|
||||
|
||||
## 0.13.0 (2023-02-22)
|
||||
|
||||
* Feature: Forward compatibility with upcoming Promise v3.
|
||||
(#188 by @clue)
|
||||
|
||||
* Feature: Full PHP 8.2 compatibility.
|
||||
(#194 and #207 by @clue)
|
||||
|
||||
* Feature: Load environment variables from `$_ENV`, `$_SERVER` and `getenv()`.
|
||||
(#205 by @clue)
|
||||
|
||||
* Feature: Update to support `Content-Length` response header on `HEAD` requests.
|
||||
(#186 by @clue)
|
||||
|
||||
* Feature / Fix: Consistent handling for HTTP responses with multiple header values (PHP SAPI).
|
||||
(#214 by @pfk84)
|
||||
|
||||
* Fix: Respect explicit response status code when Location response header is given (PHP SAPI).
|
||||
(#191 by @jkrzefski)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#189 by @clue)
|
||||
|
||||
* Add PHPStan to test environment on level `max` and improve type definitions.
|
||||
(#200, #201 and #204 by @clue)
|
||||
|
||||
* Improve test suite and report failed assertions.
|
||||
(#199 by @clue and #208 by @SimonFrings)
|
||||
|
||||
## 0.12.0 (2022-08-03)
|
||||
|
||||
* Feature: Support loading environment variables from DI container configuration.
|
||||
(#184 by @clue)
|
||||
|
||||
* Feature: Support typed container variables for container factory functions.
|
||||
(#178, #179 and #180 by @clue)
|
||||
|
||||
* Feature: Support nullable and `null` arguments and default values for DI container configuration.
|
||||
(#181 and #183 by @clue)
|
||||
|
||||
* Feature: Support untyped and `mixed` arguments for container factory.
|
||||
(#182 by @clue)
|
||||
|
||||
## 0.11.0 (2022-07-26)
|
||||
|
||||
* Feature: Make `AccessLogHandler` and `ErrorHandler` part of public API.
|
||||
(#173 and #174 by @clue)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$app = new FrameworkX\App(
|
||||
new FrameworkX\AccessLogHandler(),
|
||||
new FrameworkX\ErrorHandler()
|
||||
);
|
||||
|
||||
// Register routes here, see routing…
|
||||
|
||||
$app->run();
|
||||
```
|
||||
|
||||
* Feature: Support loading `AccessLogHandler` and `ErrorHandler` from `Container`.
|
||||
(#175 by @clue)
|
||||
|
||||
* Feature: Read `$remote_addr` attribute for `AccessLogHandler` (trusted proxies).
|
||||
(#177 by @clue)
|
||||
|
||||
* Internal refactoring to move all handlers to `Io` namespace.
|
||||
(#176 by @clue)
|
||||
|
||||
* Update test suite to remove deprecated `utf8_decode()` (PHP 8.2 preparation).
|
||||
(#171 by SimonFrings)
|
||||
|
||||
## 0.10.0 (2022-07-14)
|
||||
|
||||
* Feature: Built-in support for fibers on PHP 8.1+ with stable reactphp/async.
|
||||
(#168 by @clue)
|
||||
|
||||
```php
|
||||
$app->get('/book/{isbn}', function (Psr\Http\Message\ServerRequestInterface $request) use ($db) {
|
||||
$isbn = $request->getAttribute('isbn');
|
||||
$result = await($db->query(
|
||||
'SELECT title FROM book WHERE isbn = ?',
|
||||
[$isbn]
|
||||
));
|
||||
|
||||
assert($result instanceof React\MySQL\QueryResult);
|
||||
$data = $result->resultRows[0]['title'];
|
||||
|
||||
return React\Http\Message\Response::plaintext(
|
||||
$data
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
* Feature: Support PSR-11 container interface by using DI container as adapter.
|
||||
(#163 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#158 by @clue and #160 by @SimonFrings)
|
||||
|
||||
## 0.9.0 (2022-05-13)
|
||||
|
||||
* Feature: Add signal handling support for `SIGINT` and `SIGTERM`.
|
||||
(#150 by @clue)
|
||||
|
||||
* Feature: Improve error output for exception messages with special characters.
|
||||
(#131 by @clue)
|
||||
|
||||
* Add new documentation chapters for Docker containers and HTTP redirecting.
|
||||
(#138 by SimonFrings and #136, #151 and #156 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#143 by @zf2timo, #153 by @mattschlosser and #129 and #154 by @clue)
|
||||
|
||||
* Improve test suite and add tests for `Dockerfile` instructions.
|
||||
(#148 and #149 by @clue)
|
||||
|
||||
## 0.8.0 (2022-03-07)
|
||||
|
||||
* Feature: Automatically start new fiber for each request on PHP 8.1+.
|
||||
(#117 by @clue)
|
||||
|
||||
* Feature: Add fiber compatibility mode for PHP < 8.1.
|
||||
(#128 by @clue)
|
||||
|
||||
* Improve documentation and update installation instructions for react/async.
|
||||
(#116 and #126 by @clue and #124, #125 and #127 by @SimonFrings)
|
||||
|
||||
* Improve fiber tests to avoid now unneeded `await()` calls.
|
||||
(#118 by @clue)
|
||||
|
||||
## 0.7.0 (2022-02-05)
|
||||
|
||||
* Feature: Update to use HTTP status code constants and JSON/HTML response helpers.
|
||||
(#114 by @clue)
|
||||
|
||||
```php
|
||||
$app->get('/users/{name}', function (Psr\Http\Message\ServerRequestInterface $request) {
|
||||
return React\Http\Message\Response::plaintext(
|
||||
"Hello " . $request->getAttribute('name') . "!\n"
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
* Feature / Fix: Update to improve protocol handling for HTTP responses with no body.
|
||||
(#113 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#112 by @SimonFrings and #115 by @netcarver)
|
||||
|
||||
## 0.6.0 (2021-12-20)
|
||||
|
||||
* Feature: Support automatic dependency injection by using class names (DI container).
|
||||
(#89, #92 and #94 by @clue)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$app = new FrameworkX\App(Acme\Todo\JsonMiddleware::class);
|
||||
|
||||
$app->get('/', Acme\Todo\HelloController::class);
|
||||
$app->get('/users/{name}', Acme\Todo\UserController::class);
|
||||
|
||||
$app->run();
|
||||
```
|
||||
|
||||
* Feature: Add support for explicit DI container configuration.
|
||||
(#95, #96 and #97 by @clue)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$container = new FrameworkX\Container([
|
||||
Acme\Todo\HelloController::class => fn() => new Acme\Todo\HelloController();
|
||||
Acme\Todo\UserController::class => function (React\Http\Browser $browser) {
|
||||
// example UserController class requires two arguments:
|
||||
// - first argument will be autowired based on class reference
|
||||
// - second argument expects some manual value
|
||||
return new Acme\Todo\UserController($browser, 42);
|
||||
}
|
||||
]);
|
||||
|
||||
// …
|
||||
```
|
||||
|
||||
* Feature: Refactor to use `$_SERVER` instead of `getenv()`.
|
||||
(#91 by @bpolaszek)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#100 by @clue)
|
||||
|
||||
* Update test suite to use stable PHP 8.1 Docker image.
|
||||
(#90 by @clue)
|
||||
|
||||
## 0.5.0 (2021-11-30)
|
||||
|
||||
* Feature / BC break: Simplify `App` by always using default loop, drop optional loop instance.
|
||||
(#88 by @clue)
|
||||
|
||||
```php
|
||||
// old
|
||||
$loop = React\EventLoop\Loop::get();
|
||||
$app = new FrameworkX\App($loop);
|
||||
|
||||
// new (already supported before)
|
||||
$app = new FrameworkX\App();
|
||||
```
|
||||
|
||||
* Add documentation for manual restart of systemd service and chapter for Caddy deployment.
|
||||
(#87 by @SimonFrings and #82 by @francislavoie)
|
||||
|
||||
* Improve documentation, remove leftover `$loop` references and fix typos.
|
||||
(#72 by @shuvroroy, #80 by @Ivanshamir, #81 by @clue and #83 by @rattuscz)
|
||||
|
||||
## 0.4.0 (2021-11-23)
|
||||
|
||||
We are excited to announce the official release of Framework X to the public! 🎉🚀
|
||||
This release includes exciting new features such as full compatibility with PHP 8.1,
|
||||
improvements to response handling, and enhanced documentation covering nginx,
|
||||
Apache, and async database usage.
|
||||
|
||||
* Feature: Announce Framework X public beta.
|
||||
(#64 by @clue)
|
||||
|
||||
* Feature: Full PHP 8.1 compatibility.
|
||||
(#58 by @clue)
|
||||
|
||||
* Feature: Improve `AccessLogHandler` and fix response size for streaming response body.
|
||||
(#47, #48, #49 and #50 by @clue)
|
||||
|
||||
* Feature / Fix: Skip sending body and `Content-Length` for responses with no body.
|
||||
(#51 by @clue)
|
||||
|
||||
* Feature / Fix: Consistently reject proxy requests and handle `OPTIONS *` requests.
|
||||
(#46 by @clue)
|
||||
|
||||
* Add new documentation chapters for nginx, Apache and async database.
|
||||
(#57, #59 and #60 by @clue)
|
||||
|
||||
* Improve documentation, examples and describe HTTP caching and output buffering.
|
||||
(#52, #53, #55, #56, #61, #62 and #63 by @clue)
|
||||
|
||||
## 0.3.0 (2021-09-23)
|
||||
|
||||
* Feature: Add support for global middleware.
|
||||
(#23 by @clue)
|
||||
|
||||
* Feature: Improve error output and refactor internal error handler.
|
||||
(#37, #39 and #41 by @clue)
|
||||
|
||||
* Feature: Support changing listening address via new `X_LISTEN` environment variable.
|
||||
(#38 by @clue)
|
||||
|
||||
* Feature: Update to new ReactPHP HTTP and Socket API.
|
||||
(#26 and #29 by @HLeithner and #34 by @clue)
|
||||
|
||||
* Feature: Refactor to use new `AccessLogHandler`, `RouteHandler`, `RedirectHandler` and `SapiHandler`.
|
||||
(#42, #43, #44 and #45 by @clue)
|
||||
|
||||
* Fix: Fix path filter regex.
|
||||
(#27 by @HLeithner)
|
||||
|
||||
* Add documentation for async middleware and systemd service unit configuration.
|
||||
(#24 by @Degra1991 and #32, #35, #36 and #40 by @clue)
|
||||
|
||||
* Improve test suite and run tests on Windows with PHPUnit.
|
||||
(#31 by @SimonFrings and #28 and #33 by @clue)
|
||||
|
||||
## 0.2.0 (2021-06-18)
|
||||
|
||||
* Feature: Simplify `App` usage by making `LoopInterface` argument optional.
|
||||
(#22 by @clue)
|
||||
|
||||
```php
|
||||
// old (still supported)
|
||||
$loop = React\EventLoop\Factory::create();
|
||||
$app = new FrameworkX\App($loop);
|
||||
|
||||
// new (using default loop)
|
||||
$app = new FrameworkX\App();
|
||||
```
|
||||
|
||||
* Feature: Add middleware support.
|
||||
(#18 by @clue)
|
||||
|
||||
* Feature: Refactor and simplify route dispatcher.
|
||||
(#21 by @clue)
|
||||
|
||||
* Feature: Add Generator-based coroutine implementation.
|
||||
(#17 by @clue)
|
||||
|
||||
* Minor documentation improvements.
|
||||
(#15, #16 and #19 by @clue)
|
||||
|
||||
## 0.1.0 (2021-04-30)
|
||||
|
||||
We're excited to announce the release of the first version of Framework X in
|
||||
private beta! This version marks the starting point of our project and is the
|
||||
first of many milestones for making async PHP easier than ever before.
|
||||
|
||||
* Release Framework X, major documentation overhaul and improve examples.
|
||||
(#14, #13 and #2 by @clue)
|
||||
|
||||
* Feature: Support running behind nginx and Apache (PHP-FPM and mod_php).
|
||||
(#3, #11 and #12 by @clue)
|
||||
|
||||
* Feature / Fix: Consistently parse request URI and improve URL handling.
|
||||
(#4, #5, #6 and #7 by @clue)
|
||||
|
||||
* Feature: Rewrite `FilesystemHandler`, improve file access and directory listing.
|
||||
(#8 and #9 by @clue)
|
||||
|
||||
* Feature: Add `any()` router method to match any request method.
|
||||
(#10 by @clue)
|
||||
21
vendor/clue/framework-x/LICENSE
vendored
21
vendor/clue/framework-x/LICENSE
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Christian Lück
|
||||
|
||||
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.
|
||||
147
vendor/clue/framework-x/README.md
vendored
147
vendor/clue/framework-x/README.md
vendored
@@ -1,147 +0,0 @@
|
||||
# Framework X
|
||||
|
||||
[](https://github.com/clue-access/framework-x/actions)
|
||||
[](#tests)
|
||||
|
||||
Framework X – the simple and fast micro framework for building reactive web applications that run anywhere.
|
||||
|
||||
* [Support us](#support-us)
|
||||
* [Quickstart](#quickstart)
|
||||
* [Documentation](#documentation)
|
||||
* [Contribute](#contribute)
|
||||
* [Tests](#tests)
|
||||
* [License](#license)
|
||||
|
||||
## Support us
|
||||
|
||||
We invest a lot of time developing, maintaining and updating our awesome
|
||||
open-source projects. You can help us sustain this high-quality of our work by
|
||||
[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
|
||||
numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
|
||||
for details.
|
||||
|
||||
Let's take these projects to the next level together! 🚀
|
||||
|
||||
## Quickstart
|
||||
|
||||
Start by creating an empty project directory.
|
||||
Next, we can start by taking a look at a simple example application.
|
||||
You can use this example to get started by creating a new `public/` directory with
|
||||
an `index.php` file inside:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$app = new FrameworkX\App();
|
||||
|
||||
$app->get('/', function () {
|
||||
return React\Http\Message\Response::plaintext(
|
||||
"Hello wörld!\n"
|
||||
);
|
||||
});
|
||||
|
||||
$app->get('/users/{name}', function (Psr\Http\Message\ServerRequestInterface $request) {
|
||||
return React\Http\Message\Response::plaintext(
|
||||
"Hello " . $request->getAttribute('name') . "!\n"
|
||||
);
|
||||
});
|
||||
|
||||
$app->run();
|
||||
```
|
||||
|
||||
Next, we need to install X and its dependencies to actually run this project.
|
||||
In your project directory, simply run the following command:
|
||||
|
||||
```bash
|
||||
$ composer require clue/framework-x:^0.16
|
||||
```
|
||||
|
||||
> See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
|
||||
|
||||
That's it already! The next step is now to serve this web application.
|
||||
One of the nice properties of this project is that is works both behind
|
||||
traditional web server setups as well as in a stand-alone environment.
|
||||
|
||||
For example, you can run the above example using the built-in web server like
|
||||
this:
|
||||
|
||||
```bash
|
||||
$ php public/index.php
|
||||
```
|
||||
|
||||
You can now use your favorite web browser or command line tool to check your web
|
||||
application responds as expected:
|
||||
|
||||
```bash
|
||||
$ curl http://localhost:8080/
|
||||
Hello wörld!
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Hooked?
|
||||
See [website](https://framework-x.org/) for full documentation.
|
||||
|
||||
Found a typo or want to contribute?
|
||||
The website documentation is built from the source documentation files in
|
||||
the [docs/](docs/) folder.
|
||||
|
||||
## Contribute
|
||||
|
||||
You want to contribute to the Framework X source code or documentation? You've
|
||||
come to the right place!
|
||||
|
||||
To contribute to the source code just locate the [src/](src/) folder and you'll find all
|
||||
content in there. Additionally, our [tests/](tests/) folder contains all our unit
|
||||
tests and acceptance tests to assure our code works as expected. For more
|
||||
information on how to run the test suite check out our [testing chapter](#tests).
|
||||
|
||||
If you want to contribute to the [documentation](#documentation) of Framework X
|
||||
found on the website, take a look inside the [docs/](docs/) folder. You'll find further
|
||||
instructions inside the `README.md` in there.
|
||||
|
||||
Found a typo on our [website](https://framework-x.org/)? Simply go to our
|
||||
[website repository](https://github.com/clue/framework-x-website)
|
||||
and follow the instructions found in the `README`.
|
||||
|
||||
## Tests
|
||||
|
||||
To run the test suite, you first need to clone this repo and then install all
|
||||
dependencies [through Composer](https://getcomposer.org/):
|
||||
|
||||
```bash
|
||||
$ composer install
|
||||
```
|
||||
|
||||
To run the test suite, go to the project root and run:
|
||||
|
||||
```bash
|
||||
$ vendor/bin/phpunit
|
||||
```
|
||||
|
||||
The test suite is set up to always ensure 100% code coverage across all
|
||||
supported environments. If you have the Xdebug extension installed, you can also
|
||||
generate a code coverage report locally like this:
|
||||
|
||||
```bash
|
||||
$ XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text
|
||||
```
|
||||
|
||||
Additionally, you can run our sophisticated integration tests to verify the
|
||||
framework examples work as expected behind your web server. Use your web server
|
||||
of choice (see deployment documentation) and execute the tests with the URL to
|
||||
your installation like this:
|
||||
|
||||
```bash
|
||||
$ php tests/integration/public/index.php
|
||||
$ tests/integration.bash http://localhost:8080
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is released under the permissive [MIT license](LICENSE).
|
||||
|
||||
> Did you know that I offer custom development services and issuing invoices for
|
||||
sponsorships of releases and for contributions? Contact me (@clue) for details.
|
||||
40
vendor/clue/framework-x/composer.json
vendored
40
vendor/clue/framework-x/composer.json
vendored
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"name": "clue/framework-x",
|
||||
"description": "Framework X – the simple and fast micro framework for building reactive web applications that run anywhere.",
|
||||
"keywords": ["microframework", "micro", "framework", "web", "http", "event-driven", "async", "ReactPHP"],
|
||||
"homepage": "https://framework-x.org/",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@clue.engineering"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1",
|
||||
"nikic/fast-route": "^1.3",
|
||||
"react/async": "^4 || ^3",
|
||||
"react/http": "^1.9",
|
||||
"react/promise": "^3 || ^2.10",
|
||||
"react/socket": "^1.13"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.10.47 || 1.4.10",
|
||||
"phpunit/phpunit": "^9.6 || ^7.5",
|
||||
"psr/container": "^2 || ^1",
|
||||
"react/promise-timer": "^1.10"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FrameworkX\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"FrameworkX\\Tests\\": "tests/"
|
||||
},
|
||||
"files": [
|
||||
"tests/FiberStub.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
140
vendor/clue/framework-x/src/AccessLogHandler.php
vendored
140
vendor/clue/framework-x/src/AccessLogHandler.php
vendored
@@ -1,140 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX;
|
||||
|
||||
use FrameworkX\Io\LogStreamHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class AccessLogHandler
|
||||
{
|
||||
/** @var ?LogStreamHandler */
|
||||
private $logger;
|
||||
|
||||
/** @var bool */
|
||||
private $hasHighResolution;
|
||||
|
||||
/**
|
||||
* @param ?string $path (optional) absolute log file path or will log to console output by default
|
||||
* @throws \InvalidArgumentException if given `$path` is not an absolute file path
|
||||
* @throws \RuntimeException if given `$path` can not be opened in append mode
|
||||
*/
|
||||
public function __construct(?string $path = null)
|
||||
{
|
||||
if ($path === null) {
|
||||
$path = \PHP_SAPI === 'cli' ? 'php://output' : 'php://stderr';
|
||||
}
|
||||
|
||||
$logger = new LogStreamHandler($path);
|
||||
if (!$logger->isDevNull()) {
|
||||
// only assign logger if we're not logging to /dev/null (which would discard any logs)
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
$this->hasHighResolution = \function_exists('hrtime'); // PHP 7.3+
|
||||
}
|
||||
|
||||
/**
|
||||
* [Internal] Returns whether we're writing to /dev/null (which will discard any logs)
|
||||
*
|
||||
* @internal
|
||||
* @return bool
|
||||
*/
|
||||
public function isDevNull(): bool
|
||||
{
|
||||
return $this->logger === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>|\Generator
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request, callable $next)
|
||||
{
|
||||
if ($this->logger === null) {
|
||||
// Skip if we're logging to /dev/null (which will discard any logs).
|
||||
// As an additional optimization, the `App` will automatically
|
||||
// detect we no longer need to invoke this instance at all.
|
||||
return $next($request); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$now = $this->now();
|
||||
$response = $next($request);
|
||||
|
||||
if ($response instanceof PromiseInterface) {
|
||||
/** @var PromiseInterface<ResponseInterface> $response */
|
||||
return $response->then(function (ResponseInterface $response) use ($request, $now) {
|
||||
$this->logWhenClosed($request, $response, $now);
|
||||
return $response;
|
||||
});
|
||||
} elseif ($response instanceof \Generator) {
|
||||
return (function (\Generator $generator) use ($request, $now) {
|
||||
$response = yield from $generator;
|
||||
$this->logWhenClosed($request, $response, $now);
|
||||
return $response;
|
||||
})($response);
|
||||
} else {
|
||||
$this->logWhenClosed($request, $response, $now);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if response body is closed (not streaming) before writing log message for response
|
||||
*/
|
||||
private function logWhenClosed(ServerRequestInterface $request, ResponseInterface $response, float $start): void
|
||||
{
|
||||
$body = $response->getBody();
|
||||
|
||||
if ($body instanceof ReadableStreamInterface && $body->isReadable()) {
|
||||
$size = 0;
|
||||
$body->on('data', function (string $chunk) use (&$size) {
|
||||
$size += strlen($chunk);
|
||||
});
|
||||
|
||||
$body->on('close', function () use (&$size, $request, $response, $start) {
|
||||
$this->log($request, $response, $size, $this->now() - $start);
|
||||
});
|
||||
} else {
|
||||
$this->log($request, $response, $body->getSize() ?? strlen((string) $body), $this->now() - $start);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* writes log message for response after response body is closed (not streaming anymore)
|
||||
*/
|
||||
private function log(ServerRequestInterface $request, ResponseInterface $response, int $responseSize, float $time): void
|
||||
{
|
||||
$method = $request->getMethod();
|
||||
$status = $response->getStatusCode();
|
||||
|
||||
// HEAD requests and `204 No Content` and `304 Not Modified` always use an empty response body
|
||||
if ($method === 'HEAD' || $status === Response::STATUS_NO_CONTENT || $status === Response::STATUS_NOT_MODIFIED) {
|
||||
$responseSize = 0;
|
||||
}
|
||||
|
||||
\assert($this->logger instanceof LogStreamHandler);
|
||||
$this->logger->log(
|
||||
($request->getAttribute('remote_addr') ?? $request->getServerParams()['REMOTE_ADDR'] ?? '-') . ' ' .
|
||||
'"' . $this->escape($method) . ' ' . $this->escape($request->getRequestTarget()) . ' HTTP/' . $request->getProtocolVersion() . '" ' .
|
||||
$status . ' ' . $responseSize . ' ' . sprintf('%.3F', $time < 0 ? 0 : $time)
|
||||
);
|
||||
}
|
||||
|
||||
private function escape(string $s): string
|
||||
{
|
||||
return (string) preg_replace_callback('/[\x00-\x1F\x7F-\xFF"\\\\]+/', function (array $m) {
|
||||
return str_replace('%', '\x', rawurlencode($m[0]));
|
||||
}, $s);
|
||||
}
|
||||
|
||||
private function now(): float
|
||||
{
|
||||
return $this->hasHighResolution ? hrtime(true) * 1e-9 : microtime(true);
|
||||
}
|
||||
}
|
||||
354
vendor/clue/framework-x/src/App.php
vendored
354
vendor/clue/framework-x/src/App.php
vendored
@@ -1,354 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX;
|
||||
|
||||
use FrameworkX\Io\MiddlewareHandler;
|
||||
use FrameworkX\Io\ReactiveHandler;
|
||||
use FrameworkX\Io\RedirectHandler;
|
||||
use FrameworkX\Io\RouteHandler;
|
||||
use FrameworkX\Io\SapiHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
use function React\Async\await;
|
||||
|
||||
class App
|
||||
{
|
||||
/** @var MiddlewareHandler */
|
||||
private $handler;
|
||||
|
||||
/** @var RouteHandler */
|
||||
private $router;
|
||||
|
||||
/** @var ReactiveHandler|SapiHandler */
|
||||
private $sapi;
|
||||
|
||||
/**
|
||||
* Instantiate new X application
|
||||
*
|
||||
* ```php
|
||||
* // instantiate
|
||||
* $app = new App();
|
||||
*
|
||||
* // instantiate with global middleware
|
||||
* $app = new App($middleware);
|
||||
* $app = new App($middleware1, $middleware2);
|
||||
* ```
|
||||
*
|
||||
* @param callable|class-string ...$middleware
|
||||
*/
|
||||
public function __construct(...$middleware)
|
||||
{
|
||||
// new MiddlewareHandler([$fiberHandler, $accessLogHandler, $errorHandler, ...$middleware, $routeHandler])
|
||||
$handlers = [];
|
||||
|
||||
$container = $needsErrorHandler = new Container();
|
||||
|
||||
// only log for built-in webserver and PHP development webserver by default, others have their own access log
|
||||
$needsAccessLog = (\PHP_SAPI === 'cli' || \PHP_SAPI === 'cli-server') ? $container : null;
|
||||
|
||||
if ($middleware) {
|
||||
$needsErrorHandlerNext = false;
|
||||
foreach ($middleware as $handler) {
|
||||
// load AccessLogHandler and ErrorHandler instance from last Container
|
||||
if ($handler === AccessLogHandler::class) {
|
||||
$handler = $container->getAccessLogHandler();
|
||||
} elseif ($handler === ErrorHandler::class) {
|
||||
$handler = $container->getErrorHandler();
|
||||
}
|
||||
|
||||
// ensure AccessLogHandler is always followed by ErrorHandler
|
||||
if ($needsErrorHandlerNext && !$handler instanceof ErrorHandler) {
|
||||
break;
|
||||
}
|
||||
$needsErrorHandlerNext = false;
|
||||
|
||||
if ($handler instanceof Container) {
|
||||
// remember last Container to load any following class names
|
||||
$container = $handler;
|
||||
|
||||
// add default ErrorHandler from last Container before adding any other handlers, may be followed by other Container instances (unlikely)
|
||||
if (!$handlers) {
|
||||
$needsErrorHandler = $needsAccessLog = $container;
|
||||
}
|
||||
} elseif (!\is_callable($handler)) {
|
||||
$handlers[] = $container->callable($handler);
|
||||
} else {
|
||||
// don't need a default ErrorHandler if we're adding one as first handler or AccessLogHandler as first followed by one
|
||||
if ($needsErrorHandler && ($handler instanceof ErrorHandler || $handler instanceof AccessLogHandler) && !$handlers) {
|
||||
$needsErrorHandler = null;
|
||||
}
|
||||
|
||||
// only add to list of handlers if this is not a NOOP
|
||||
if (!$handler instanceof AccessLogHandler || !$handler->isDevNull()) {
|
||||
$handlers[] = $handler;
|
||||
}
|
||||
|
||||
if ($handler instanceof AccessLogHandler) {
|
||||
$needsAccessLog = null;
|
||||
$needsErrorHandlerNext = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($needsErrorHandlerNext) {
|
||||
throw new \TypeError('AccessLogHandler must be followed by ErrorHandler');
|
||||
}
|
||||
}
|
||||
|
||||
// add default ErrorHandler as first handler unless it is already added explicitly
|
||||
if ($needsErrorHandler instanceof Container) {
|
||||
\array_unshift($handlers, $needsErrorHandler->getErrorHandler());
|
||||
}
|
||||
|
||||
// only log for built-in webserver and PHP development webserver by default, others have their own access log
|
||||
if ($needsAccessLog instanceof Container) {
|
||||
$handler = $needsAccessLog->getAccessLogHandler();
|
||||
if (!$handler->isDevNull()) {
|
||||
\array_unshift($handlers, $handler);
|
||||
}
|
||||
}
|
||||
|
||||
$this->router = new RouteHandler($container);
|
||||
$handlers[] = $this->router;
|
||||
$this->handler = new MiddlewareHandler($handlers);
|
||||
$this->sapi = \PHP_SAPI === 'cli' ? new ReactiveHandler($container->getEnv('X_LISTEN')) : new SapiHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function get(string $route, $handler, ...$handlers): void
|
||||
{
|
||||
$this->map(['GET'], $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function head(string $route, $handler, ...$handlers): void
|
||||
{
|
||||
$this->map(['HEAD'], $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function post(string $route, $handler, ...$handlers): void
|
||||
{
|
||||
$this->map(['POST'], $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function put(string $route, $handler, ...$handlers): void
|
||||
{
|
||||
$this->map(['PUT'], $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function patch(string $route, $handler, ...$handlers): void
|
||||
{
|
||||
$this->map(['PATCH'], $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function delete(string $route, $handler, ...$handlers): void
|
||||
{
|
||||
$this->map(['DELETE'], $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function options(string $route, $handler, ...$handlers): void
|
||||
{
|
||||
// backward compatibility: `OPTIONS * HTTP/1.1` can be matched with empty path (legacy)
|
||||
if ($route === '') {
|
||||
$route = '*';
|
||||
}
|
||||
|
||||
$this->map(['OPTIONS'], $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function any(string $route, $handler, ...$handlers): void
|
||||
{
|
||||
$this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string[] $methods
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function map(array $methods, string $route, $handler, ...$handlers): void
|
||||
{
|
||||
$this->router->map($methods, $route, $handler, ...$handlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $target
|
||||
* @param int $code
|
||||
*/
|
||||
public function redirect(string $route, string $target, int $code = Response::STATUS_FOUND): void
|
||||
{
|
||||
$this->any($route, new RedirectHandler($target, $code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the app to handle HTTP requests according to any registered routes and middleware.
|
||||
*
|
||||
* This is where the magic happens: When executed on the command line (CLI),
|
||||
* this will run the powerful reactive request handler built on top of
|
||||
* ReactPHP. This works by running the efficient built-in HTTP web server to
|
||||
* handle incoming HTTP requests through ReactPHP's HTTP and socket server.
|
||||
* This async execution mode is usually recommended as it can efficiently
|
||||
* process a large number of concurrent connections and process multiple
|
||||
* incoming requests simultaneously. The long-running server process will
|
||||
* continue to run until it is interrupted by a signal.
|
||||
*
|
||||
* When executed behind traditional PHP SAPIs (PHP-FPM, FastCGI, Apache, etc.),
|
||||
* this will handle a single request and run until a single response is sent.
|
||||
* This is particularly useful because it allows you to run the exact same
|
||||
* app in any environment.
|
||||
*
|
||||
* @see ReactiveHandler::run()
|
||||
* @see SapiHandler::run()
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$this->sapi->run(\Closure::fromCallable([$this, 'handleRequest']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the app to handle a single HTTP request according to any registered routes and middleware.
|
||||
*
|
||||
* This method allows you to pass in a single HTTP request object that will
|
||||
* be processed according to any registered routes and middleware and will
|
||||
* return an HTTP response object as a result.
|
||||
*
|
||||
* ```php
|
||||
* $app = new FrameworkX\App();
|
||||
* $app->get('/', fn() => React\Http\Message\Response::plaintext("Hello!\n"));
|
||||
*
|
||||
* $request = new React\Http\Message\ServerRequest('GET', 'https://example.com/');
|
||||
* $response = $app($request);
|
||||
*
|
||||
* assert($response instanceof Psr\Http\Message\ResponseInterface);
|
||||
* assert($response->getStatusCode() === 200);
|
||||
* assert($response->getBody()->getContents() === "Hello\n");
|
||||
* ```
|
||||
*
|
||||
* This is particularly useful for higher-level integration test suites and
|
||||
* for custom integrations with other runtime environments like serverless
|
||||
* functions or other frameworks. Otherwise, most applications would likely
|
||||
* want to use the `run()` method to run the application and automatically
|
||||
* accept incoming HTTP requests according to the PHP SAPI in use.
|
||||
*
|
||||
* @param ServerRequestInterface $request The HTTP request object to process.
|
||||
* @return ResponseInterface This method returns an HTTP response object
|
||||
* according to any registered routes and middleware. If any handler is
|
||||
* async, it will await its execution before returning, running the
|
||||
* event loop as needed. If the request can not be routed or any handler
|
||||
* fails, it will return a matching HTTP error response object.
|
||||
* @throws void This method never throws. If the request can not be routed
|
||||
* or any handler fails, it will be turned into a valid error response
|
||||
* before returning.
|
||||
* @see self::run()
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$response = $this->handleRequest($request);
|
||||
if ($response instanceof PromiseInterface) {
|
||||
/** @throws void */
|
||||
$response = await($response);
|
||||
assert($response instanceof ResponseInterface);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>
|
||||
* Returns a response or a Promise which eventually fulfills with a
|
||||
* response. This method never throws or resolves a rejected promise.
|
||||
* If the request can not be routed or the handler fails, it will be
|
||||
* turned into a valid error response before returning.
|
||||
* @throws void
|
||||
*/
|
||||
private function handleRequest(ServerRequestInterface $request)
|
||||
{
|
||||
$response = ($this->handler)($request);
|
||||
assert($response instanceof ResponseInterface || $response instanceof PromiseInterface || $response instanceof \Generator);
|
||||
|
||||
if ($response instanceof \Generator) {
|
||||
if ($response->valid()) {
|
||||
$response = $this->coroutine($response);
|
||||
} else {
|
||||
$response = $response->getReturn();
|
||||
assert($response instanceof ResponseInterface);
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PromiseInterface<ResponseInterface>
|
||||
*/
|
||||
private function coroutine(\Generator $generator): PromiseInterface
|
||||
{
|
||||
$next = null;
|
||||
$deferred = new Deferred();
|
||||
$next = function () use ($generator, &$next, $deferred) {
|
||||
if (!$generator->valid()) {
|
||||
$deferred->resolve($generator->getReturn());
|
||||
return;
|
||||
}
|
||||
|
||||
$promise = $generator->current();
|
||||
assert($promise instanceof PromiseInterface);
|
||||
|
||||
$promise->then(function ($value) use ($generator, $next) {
|
||||
$generator->send($value);
|
||||
$next();
|
||||
}, function ($reason) use ($generator, $next) {
|
||||
$generator->throw($reason);
|
||||
$next();
|
||||
});
|
||||
};
|
||||
|
||||
$next();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
382
vendor/clue/framework-x/src/Container.php
vendored
382
vendor/clue/framework-x/src/Container.php
vendored
@@ -1,382 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class Container
|
||||
{
|
||||
/** @var array<string,object|callable():(object|scalar|null)|scalar|null>|ContainerInterface */
|
||||
private $container;
|
||||
|
||||
/** @var bool */
|
||||
private $useProcessEnv;
|
||||
|
||||
/** @param array<string,callable():(object|scalar|null) | object | scalar | null>|ContainerInterface $loader */
|
||||
public function __construct($loader = [])
|
||||
{
|
||||
/** @var mixed $loader explicit type check for mixed if user ignores parameter type */
|
||||
if (!\is_array($loader) && !$loader instanceof ContainerInterface) {
|
||||
throw new \TypeError(
|
||||
'Argument #1 ($loader) must be of type array|Psr\Container\ContainerInterface, ' . (\is_object($loader) ? get_class($loader) : gettype($loader)) . ' given'
|
||||
);
|
||||
}
|
||||
|
||||
foreach (($loader instanceof ContainerInterface ? [] : $loader) as $name => $value) {
|
||||
if (
|
||||
(!\is_object($value) && !\is_scalar($value) && $value !== null) ||
|
||||
(!$value instanceof $name && !$value instanceof \Closure && !\is_string($value) && \strpos($name, '\\') !== false)
|
||||
) {
|
||||
throw new \BadMethodCallException('Map for ' . $name . ' contains unexpected ' . (is_object($value) ? get_class($value) : gettype($value)));
|
||||
}
|
||||
}
|
||||
$this->container = $loader;
|
||||
|
||||
// prefer reading environment from `$_ENV` and `$_SERVER`, only fall back to `getenv()` in thread-safe environments
|
||||
$this->useProcessEnv = \ZEND_THREAD_SAFE === false || \in_array(\PHP_SAPI, ['cli', 'cli-server', 'cgi-fcgi', 'fpm-fcgi'], true);
|
||||
}
|
||||
|
||||
/** @return mixed */
|
||||
public function __invoke(ServerRequestInterface $request, callable $next = null)
|
||||
{
|
||||
if ($next === null) {
|
||||
// You don't want to end up here. This only happens if you use the
|
||||
// container as a final request handler instead of as a middleware.
|
||||
// In this case, you should omit the container or add another final
|
||||
// request handler behind the container in the middleware chain.
|
||||
throw new \BadMethodCallException('Container should not be used as final request handler');
|
||||
}
|
||||
|
||||
// If the container is used as a middleware, simply forward to the next
|
||||
// request handler. As an additional optimization, the container would
|
||||
// usually be filtered out from a middleware chain as this is a NO-OP.
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $class
|
||||
* @return callable(ServerRequestInterface,?callable=null)
|
||||
* @internal
|
||||
*/
|
||||
public function callable(string $class): callable
|
||||
{
|
||||
return function (ServerRequestInterface $request, callable $next = null) use ($class) {
|
||||
// Check `$class` references a valid class name that can be autoloaded
|
||||
if (\is_array($this->container) && !\class_exists($class, true) && !interface_exists($class, false) && !trait_exists($class, false)) {
|
||||
throw new \BadMethodCallException('Request handler class ' . $class . ' not found');
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->container instanceof ContainerInterface) {
|
||||
$handler = $this->container->get($class);
|
||||
} else {
|
||||
$handler = $this->loadObject($class);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
throw new \BadMethodCallException(
|
||||
'Request handler class ' . $class . ' failed to load: ' . $e->getMessage(),
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
// Check `$handler` references a class name that is callable, i.e. has an `__invoke()` method.
|
||||
// This initial version is intentionally limited to checking the method name only.
|
||||
// A follow-up version will likely use reflection to check request handler argument types.
|
||||
if (!is_callable($handler)) {
|
||||
throw new \BadMethodCallException('Request handler class "' . $class . '" has no public __invoke() method');
|
||||
}
|
||||
|
||||
// invoke request handler as middleware handler or final controller
|
||||
if ($next === null) {
|
||||
return $handler($request);
|
||||
}
|
||||
return $handler($request, $next);
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function getEnv(string $name): ?string
|
||||
{
|
||||
assert(\preg_match('/^[A-Z][A-Z0-9_]+$/', $name) === 1);
|
||||
|
||||
if ($this->container instanceof ContainerInterface && $this->container->has($name)) {
|
||||
$value = $this->container->get($name);
|
||||
} elseif ($this->hasVariable($name)) {
|
||||
$value = $this->loadVariable($name, 'mixed', true, 64);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!\is_string($value) && $value !== null) {
|
||||
throw new \TypeError('Environment variable $' . $name . ' expected type string|null, but got ' . (\is_object($value) ? \get_class($value) : \gettype($value)));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function getAccessLogHandler(): AccessLogHandler
|
||||
{
|
||||
if ($this->container instanceof ContainerInterface) {
|
||||
if ($this->container->has(AccessLogHandler::class)) {
|
||||
// @phpstan-ignore-next-line method return type will ensure correct type or throw `TypeError`
|
||||
return $this->container->get(AccessLogHandler::class);
|
||||
} else {
|
||||
return new AccessLogHandler();
|
||||
}
|
||||
}
|
||||
return $this->loadObject(AccessLogHandler::class);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function getErrorHandler(): ErrorHandler
|
||||
{
|
||||
if ($this->container instanceof ContainerInterface) {
|
||||
if ($this->container->has(ErrorHandler::class)) {
|
||||
// @phpstan-ignore-next-line method return type will ensure correct type or throw `TypeError`
|
||||
return $this->container->get(ErrorHandler::class);
|
||||
} else {
|
||||
return new ErrorHandler();
|
||||
}
|
||||
}
|
||||
return $this->loadObject(ErrorHandler::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
* @param class-string<T> $name
|
||||
* @return T
|
||||
* @throws \BadMethodCallException if object of type $name can not be loaded
|
||||
*/
|
||||
private function loadObject(string $name, int $depth = 64) /*: object (PHP 7.2+) */
|
||||
{
|
||||
assert(\is_array($this->container));
|
||||
|
||||
if (\array_key_exists($name, $this->container)) {
|
||||
if (\is_string($this->container[$name])) {
|
||||
if ($depth < 1) {
|
||||
throw new \BadMethodCallException('Factory for ' . $name . ' is recursive');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line because type of container value is explicitly checked after getting here
|
||||
$value = $this->loadObject($this->container[$name], $depth - 1);
|
||||
if (!$value instanceof $name) {
|
||||
throw new \BadMethodCallException('Factory for ' . $name . ' returned unexpected ' . \get_class($value));
|
||||
}
|
||||
|
||||
$this->container[$name] = $value;
|
||||
} elseif ($this->container[$name] instanceof \Closure) {
|
||||
// build list of factory parameters based on parameter types
|
||||
$closure = new \ReflectionFunction($this->container[$name]);
|
||||
$params = $this->loadFunctionParams($closure, $depth, true);
|
||||
|
||||
// invoke factory with list of parameters
|
||||
$value = $params === [] ? ($this->container[$name])() : ($this->container[$name])(...$params);
|
||||
|
||||
if (\is_string($value)) {
|
||||
if ($depth < 1) {
|
||||
throw new \BadMethodCallException('Factory for ' . $name . ' is recursive');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line because type of container value is explicitly checked after getting here
|
||||
$value = $this->loadObject($value, $depth - 1);
|
||||
}
|
||||
if (!$value instanceof $name) {
|
||||
throw new \BadMethodCallException('Factory for ' . $name . ' returned unexpected ' . (is_object($value) ? get_class($value) : gettype($value)));
|
||||
}
|
||||
|
||||
$this->container[$name] = $value;
|
||||
} elseif (!$this->container[$name] instanceof $name) {
|
||||
throw new \BadMethodCallException('Map for ' . $name . ' contains unexpected ' . (\is_object($this->container[$name]) ? \get_class($this->container[$name]) : \gettype($this->container[$name])));
|
||||
}
|
||||
|
||||
assert($this->container[$name] instanceof $name);
|
||||
|
||||
return $this->container[$name];
|
||||
}
|
||||
|
||||
// Check `$name` references a valid class name that can be autoloaded
|
||||
if (!\class_exists($name, true) && !interface_exists($name, false) && !trait_exists($name, false)) {
|
||||
throw new \BadMethodCallException('Class ' . $name . ' not found');
|
||||
}
|
||||
|
||||
$class = new \ReflectionClass($name);
|
||||
if (!$class->isInstantiable()) {
|
||||
$modifier = 'class';
|
||||
if ($class->isInterface()) {
|
||||
$modifier = 'interface';
|
||||
} elseif ($class->isAbstract()) {
|
||||
$modifier = 'abstract class';
|
||||
} elseif ($class->isTrait()) {
|
||||
$modifier = 'trait';
|
||||
}
|
||||
throw new \BadMethodCallException('Cannot instantiate ' . $modifier . ' '. $name);
|
||||
}
|
||||
|
||||
// build list of constructor parameters based on parameter types
|
||||
$ctor = $class->getConstructor();
|
||||
$params = $ctor === null ? [] : $this->loadFunctionParams($ctor, $depth, false);
|
||||
|
||||
// instantiate with list of parameters
|
||||
// @phpstan-ignore-next-line because `$class->newInstance()` is known to return `T`
|
||||
return $this->container[$name] = $params === [] ? new $name() : $class->newInstance(...$params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
* @throws \BadMethodCallException if either parameter can not be loaded
|
||||
*/
|
||||
private function loadFunctionParams(\ReflectionFunctionAbstract $function, int $depth, bool $allowVariables): array
|
||||
{
|
||||
$params = [];
|
||||
foreach ($function->getParameters() as $parameter) {
|
||||
$params[] = $this->loadParameter($parameter, $depth, $allowVariables);
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws \BadMethodCallException if $parameter can not be loaded
|
||||
*/
|
||||
private function loadParameter(\ReflectionParameter $parameter, int $depth, bool $allowVariables) /*: mixed (PHP 8.0+) */
|
||||
{
|
||||
assert(\is_array($this->container));
|
||||
|
||||
$type = $parameter->getType();
|
||||
$hasDefault = $parameter->isDefaultValueAvailable() || ((!$type instanceof \ReflectionNamedType || $type->getName() !== 'mixed') && $parameter->allowsNull());
|
||||
|
||||
// abort for union types (PHP 8.0+) and intersection types (PHP 8.1+)
|
||||
// @phpstan-ignore-next-line for PHP < 8
|
||||
if ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { // @codeCoverageIgnoreStart
|
||||
if ($hasDefault) {
|
||||
return $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
|
||||
}
|
||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' expects unsupported type ' . $type);
|
||||
} // @codeCoverageIgnoreEnd
|
||||
|
||||
// load container variables if parameter name is known
|
||||
assert($type === null || $type instanceof \ReflectionNamedType);
|
||||
if ($allowVariables && $this->hasVariable($parameter->getName())) {
|
||||
return $this->loadVariable($parameter->getName(), $type === null ? 'mixed' : $type->getName(), $parameter->allowsNull(), $depth);
|
||||
}
|
||||
|
||||
// abort if parameter is untyped and not explicitly defined by container variable
|
||||
if ($type === null) {
|
||||
assert($parameter->allowsNull());
|
||||
if ($parameter->isDefaultValueAvailable()) {
|
||||
return $parameter->getDefaultValue();
|
||||
}
|
||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' has no type');
|
||||
}
|
||||
|
||||
// use default/nullable argument if not loadable as container variable or by type
|
||||
assert($type instanceof \ReflectionNamedType);
|
||||
if ($hasDefault && ($type->isBuiltin() || !\array_key_exists($type->getName(), $this->container))) {
|
||||
return $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
|
||||
}
|
||||
|
||||
// abort if required container variable is not defined or for any other primitive types (array etc.)
|
||||
if ($type->isBuiltin()) {
|
||||
if ($allowVariables) {
|
||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' is not defined');
|
||||
} else {
|
||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' expects unsupported type ' . $type->getName());
|
||||
}
|
||||
}
|
||||
|
||||
// abort for unreasonably deep nesting or recursive types
|
||||
if ($depth < 1) {
|
||||
throw new \BadMethodCallException(self::parameterError($parameter) . ' is recursive');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line because `$type->getName()` is a `class-string` by definition
|
||||
return $this->loadObject($type->getName(), $depth - 1);
|
||||
}
|
||||
|
||||
private function hasVariable(string $name): bool
|
||||
{
|
||||
return (\is_array($this->container) && \array_key_exists($name, $this->container)) || (isset($_ENV[$name]) || (\is_string($_SERVER[$name] ?? null) || ($this->useProcessEnv && \getenv($name) !== false)) && \preg_match('/^[A-Z][A-Z0-9_]+$/', $name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object|string|int|float|bool|null
|
||||
* @throws \BadMethodCallException if $name is not a valid container variable
|
||||
*/
|
||||
private function loadVariable(string $name, string $type, bool $nullable, int $depth) /*: object|string|int|float|bool|null (PHP 8.0+) */
|
||||
{
|
||||
assert($this->hasVariable($name));
|
||||
assert(\is_array($this->container) || !$this->container->has($name));
|
||||
|
||||
if (\is_array($this->container) && ($this->container[$name] ?? null) instanceof \Closure) {
|
||||
if ($depth < 1) {
|
||||
throw new \BadMethodCallException('Container variable $' . $name . ' is recursive');
|
||||
}
|
||||
|
||||
// build list of factory parameters based on parameter types
|
||||
$factory = $this->container[$name];
|
||||
assert($factory instanceof \Closure);
|
||||
$closure = new \ReflectionFunction($factory);
|
||||
$params = $this->loadFunctionParams($closure, $depth - 1, true);
|
||||
|
||||
// invoke factory with list of parameters
|
||||
$value = $params === [] ? $factory() : $factory(...$params);
|
||||
|
||||
if (!\is_object($value) && !\is_scalar($value) && $value !== null) {
|
||||
throw new \BadMethodCallException('Container variable $' . $name . ' expected type object|scalar|null from factory, but got ' . \gettype($value));
|
||||
}
|
||||
|
||||
$this->container[$name] = $value;
|
||||
} elseif (\is_array($this->container) && \array_key_exists($name, $this->container)) {
|
||||
$value = $this->container[$name];
|
||||
} elseif (isset($_ENV[$name])) {
|
||||
assert(\is_string($_ENV[$name]));
|
||||
$value = $_ENV[$name];
|
||||
} elseif (isset($_SERVER[$name])) {
|
||||
assert(\is_string($_SERVER[$name]));
|
||||
$value = $_SERVER[$name];
|
||||
} else {
|
||||
$value = \getenv($name);
|
||||
assert($this->useProcessEnv && $value !== false);
|
||||
}
|
||||
|
||||
assert(\is_object($value) || \is_scalar($value) || $value === null);
|
||||
|
||||
// allow null values if parameter is marked nullable or untyped or mixed
|
||||
if ($nullable && $value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// skip type checks and allow all values if expected type is undefined or mixed (PHP 8+)
|
||||
if ($type === 'mixed') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (
|
||||
(\is_object($value) && !$value instanceof $type) ||
|
||||
(!\is_object($value) && !\in_array($type, ['string', 'int', 'float', 'bool'])) ||
|
||||
($type === 'string' && !\is_string($value)) || ($type === 'int' && !\is_int($value)) || ($type === 'float' && !\is_float($value)) || ($type === 'bool' && !\is_bool($value))
|
||||
) {
|
||||
throw new \BadMethodCallException('Container variable $' . $name . ' expected type ' . $type . ', but got ' . (\is_object($value) ? \get_class($value) : \gettype($value)));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** @throws void */
|
||||
private static function parameterError(\ReflectionParameter $parameter): string
|
||||
{
|
||||
$name = $parameter->getDeclaringFunction()->getShortName();
|
||||
if (!$parameter->getDeclaringFunction()->isClosure() && ($class = $parameter->getDeclaringClass()) !== null) {
|
||||
$name = explode("\0", $class->getName())[0] . '::' . $name;
|
||||
}
|
||||
|
||||
return 'Argument ' . ($parameter->getPosition() + 1) . ' ($' . $parameter->getName() . ') of ' . $name . '()';
|
||||
}
|
||||
}
|
||||
211
vendor/clue/framework-x/src/ErrorHandler.php
vendored
211
vendor/clue/framework-x/src/ErrorHandler.php
vendored
@@ -1,211 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX;
|
||||
|
||||
use FrameworkX\Io\HtmlHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*/
|
||||
class ErrorHandler
|
||||
{
|
||||
/** @var Htmlhandler */
|
||||
private $html;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->html = new HtmlHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>|\Generator
|
||||
* Returns a response, a Promise which eventually fulfills with a
|
||||
* response or a Generator which eventually returns a response. This
|
||||
* method never throws or resolves a rejected promise. If the next
|
||||
* handler fails to return a valid response, it will be turned into a
|
||||
* valid error response before returning.
|
||||
* @throws void
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request, callable $next)
|
||||
{
|
||||
try {
|
||||
$response = $next($request);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->errorInvalidException($e);
|
||||
}
|
||||
|
||||
if ($response instanceof ResponseInterface) {
|
||||
return $response;
|
||||
} elseif ($response instanceof PromiseInterface) {
|
||||
return $response->then(function ($response) {
|
||||
if ($response instanceof ResponseInterface) {
|
||||
return $response;
|
||||
} else {
|
||||
return $this->errorInvalidResponse($response);
|
||||
}
|
||||
}, function ($e) {
|
||||
// Promise rejected, always a `\Throwable` as of Promise v3
|
||||
assert($e instanceof \Throwable || !\method_exists(PromiseInterface::class, 'catch')); // @phpstan-ignore-line
|
||||
|
||||
if ($e instanceof \Throwable) {
|
||||
return $this->errorInvalidException($e);
|
||||
} else { // @phpstan-ignore-line
|
||||
// @phpstan-ignore-next-line
|
||||
return $this->errorInvalidResponse(\React\Promise\reject($e)); // @codeCoverageIgnore
|
||||
}
|
||||
});
|
||||
} elseif ($response instanceof \Generator) {
|
||||
return $this->coroutine($response);
|
||||
} else {
|
||||
return $this->errorInvalidResponse($response);
|
||||
}
|
||||
}
|
||||
|
||||
private function coroutine(\Generator $generator): \Generator
|
||||
{
|
||||
do {
|
||||
try {
|
||||
if (!$generator->valid()) {
|
||||
$response = $generator->getReturn();
|
||||
if ($response instanceof ResponseInterface) {
|
||||
return $response;
|
||||
} else {
|
||||
return $this->errorInvalidResponse($response);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return $this->errorInvalidException($e);
|
||||
}
|
||||
|
||||
$promise = $generator->current();
|
||||
if (!$promise instanceof PromiseInterface) {
|
||||
$gref = new \ReflectionGenerator($generator);
|
||||
|
||||
return $this->errorInvalidCoroutine(
|
||||
$promise,
|
||||
$gref->getExecutingFile(),
|
||||
$gref->getExecutingLine()
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$next = yield $promise;
|
||||
} catch (\Throwable $e) {
|
||||
try {
|
||||
$generator->throw($e);
|
||||
continue;
|
||||
} catch (\Throwable $e) {
|
||||
return $this->errorInvalidException($e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$generator->send($next);
|
||||
} catch (\Throwable $e) {
|
||||
return $this->errorInvalidException($e);
|
||||
}
|
||||
} while (true);
|
||||
} // @codeCoverageIgnore
|
||||
|
||||
/** @internal */
|
||||
public function requestNotFound(): ResponseInterface
|
||||
{
|
||||
return $this->htmlResponse(
|
||||
Response::STATUS_NOT_FOUND,
|
||||
'Page Not Found',
|
||||
'Please check the URL in the address bar and try again.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param list<string> $allowedMethods
|
||||
*/
|
||||
public function requestMethodNotAllowed(array $allowedMethods): ResponseInterface
|
||||
{
|
||||
$methods = \implode('/', \array_map(function (string $method) { return '<code>' . $method . '</code>'; }, $allowedMethods));
|
||||
|
||||
return $this->htmlResponse(
|
||||
Response::STATUS_METHOD_NOT_ALLOWED,
|
||||
'Method Not Allowed',
|
||||
'Please check the URL in the address bar and try again with ' . $methods . ' request.'
|
||||
)->withHeader('Allow', \implode(', ', $allowedMethods));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function requestProxyUnsupported(): ResponseInterface
|
||||
{
|
||||
return $this->htmlResponse(
|
||||
Response::STATUS_BAD_REQUEST,
|
||||
'Proxy Requests Not Allowed',
|
||||
'Please check your settings and retry.'
|
||||
);
|
||||
}
|
||||
|
||||
private function errorInvalidException(\Throwable $e): ResponseInterface
|
||||
{
|
||||
$where = ' in ' . $this->where($e->getFile(), $e->getLine());
|
||||
$message = '<code>' . $this->html->escape($e->getMessage()) . '</code>';
|
||||
|
||||
return $this->htmlResponse(
|
||||
Response::STATUS_INTERNAL_SERVER_ERROR,
|
||||
'Internal Server Error',
|
||||
'The requested page failed to load, please try again later.',
|
||||
'Expected request handler to return <code>' . ResponseInterface::class . '</code> but got uncaught <code>' . \get_class($e) . '</code> with message ' . $message . $where . '.'
|
||||
);
|
||||
}
|
||||
|
||||
/** @param mixed $value */
|
||||
private function errorInvalidResponse($value): ResponseInterface
|
||||
{
|
||||
return $this->htmlResponse(
|
||||
Response::STATUS_INTERNAL_SERVER_ERROR,
|
||||
'Internal Server Error',
|
||||
'The requested page failed to load, please try again later.',
|
||||
'Expected request handler to return <code>' . ResponseInterface::class . '</code> but got <code>' . $this->describeType($value) . '</code>.'
|
||||
);
|
||||
}
|
||||
|
||||
/** @param mixed $value */
|
||||
private function errorInvalidCoroutine($value, string $file, int $line): ResponseInterface
|
||||
{
|
||||
$where = ' near or before '. $this->where($file, $line) . '.';
|
||||
|
||||
return $this->htmlResponse(
|
||||
Response::STATUS_INTERNAL_SERVER_ERROR,
|
||||
'Internal Server Error',
|
||||
'The requested page failed to load, please try again later.',
|
||||
'Expected request handler to yield <code>' . PromiseInterface::class . '</code> but got <code>' . $this->describeType($value) . '</code>' . $where
|
||||
);
|
||||
}
|
||||
|
||||
private function where(string $file, int $line): string
|
||||
{
|
||||
return '<code title="See ' . $file . ' line ' . $line . '">' . \basename($file) . ':' . $line . '</code>';
|
||||
}
|
||||
|
||||
private function htmlResponse(int $statusCode, string $title, string ...$info): ResponseInterface
|
||||
{
|
||||
return $this->html->statusResponse(
|
||||
$statusCode,
|
||||
'Error ' . $statusCode . ': ' .$title,
|
||||
$title,
|
||||
\implode('', \array_map(function (string $info) { return "<p>$info</p>\n"; }, $info))
|
||||
);
|
||||
}
|
||||
|
||||
/** @param mixed $value */
|
||||
private function describeType($value): string
|
||||
{
|
||||
if ($value === null) {
|
||||
return 'null';
|
||||
} elseif (\is_scalar($value) && !\is_string($value)) {
|
||||
return \var_export($value, true);
|
||||
}
|
||||
return \is_object($value) ? \get_class($value) : \gettype($value);
|
||||
}
|
||||
}
|
||||
130
vendor/clue/framework-x/src/FilesystemHandler.php
vendored
130
vendor/clue/framework-x/src/FilesystemHandler.php
vendored
@@ -1,130 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX;
|
||||
|
||||
use FrameworkX\Io\HtmlHandler;
|
||||
use FrameworkX\Io\RedirectHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Http\Message\Response;
|
||||
|
||||
class FilesystemHandler
|
||||
{
|
||||
/** @var string */
|
||||
private $root;
|
||||
|
||||
/**
|
||||
* Mapping between file extension and MIME type to send in `Content-Type` response header
|
||||
*
|
||||
* @var array<string,string>
|
||||
*/
|
||||
private $mimetypes = array(
|
||||
'atom' => 'application/atom+xml',
|
||||
'bz2' => 'application/x-bzip2',
|
||||
'css' => 'text/css',
|
||||
'gif' => 'image/gif',
|
||||
'gz' => 'application/gzip',
|
||||
'htm' => 'text/html',
|
||||
'html' => 'text/html',
|
||||
'ico' => 'image/x-icon',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'jpg' => 'image/jpeg',
|
||||
'js' => 'text/javascript',
|
||||
'json' => 'application/json',
|
||||
'pdf' => 'application/pdf',
|
||||
'png' => 'image/png',
|
||||
'rss' => 'application/rss+xml',
|
||||
'svg' => 'image/svg+xml',
|
||||
'tar' => 'application/x-tar',
|
||||
'xml' => 'application/xml',
|
||||
'zip' => 'application/zip',
|
||||
);
|
||||
|
||||
/**
|
||||
* Assign default MIME type to send in `Content-Type` response header (same as nginx/Apache)
|
||||
*
|
||||
* @var string
|
||||
* @see self::$mimetypes
|
||||
*/
|
||||
private $defaultMimetype = 'text/plain';
|
||||
|
||||
/** @var ErrorHandler */
|
||||
private $errorHandler;
|
||||
|
||||
/** @var HtmlHandler */
|
||||
private $html;
|
||||
|
||||
public function __construct(string $root)
|
||||
{
|
||||
$this->root = $root;
|
||||
$this->errorHandler = new ErrorHandler();
|
||||
$this->html = new HtmlHandler();
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$local = $request->getAttribute('path', '');
|
||||
assert(\is_string($local));
|
||||
$path = \rtrim($this->root . '/' . $local, '/');
|
||||
|
||||
// local path should not contain "./", "../", "//" or null bytes or start with slash
|
||||
$valid = !\preg_match('#(?:^|/)\.\.?(?:$|/)|^/|//|\x00#', $local);
|
||||
|
||||
\clearstatcache();
|
||||
if ($valid && \is_dir($path)) {
|
||||
if ($local !== '' && \substr($local, -1) !== '/') {
|
||||
return (new RedirectHandler(\basename($path) . '/'))();
|
||||
}
|
||||
|
||||
$response = '<strong>' . $this->html->escape($local === '' ? '/' : $local) . '</strong>' . "\n<ul>\n";
|
||||
|
||||
if ($local !== '') {
|
||||
$response .= ' <li><a href="../">../</a></li>' . "\n";
|
||||
}
|
||||
|
||||
$files = \scandir($path);
|
||||
// @phpstan-ignore-next-line TODO handle error if directory can not be accessed
|
||||
foreach ($files as $file) {
|
||||
if ($file === '.' || $file === '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$dir = \is_dir($path . '/' . $file) ? '/' : '';
|
||||
$response .= ' <li><a href="' . \rawurlencode($file) . $dir . '">' . $this->html->escape($file) . $dir . '</a></li>' . "\n";
|
||||
}
|
||||
$response .= '</ul>' . "\n";
|
||||
|
||||
return Response::html(
|
||||
$response
|
||||
);
|
||||
} elseif ($valid && \is_file($path)) {
|
||||
if ($local !== '' && \substr($local, -1) === '/') {
|
||||
return (new RedirectHandler('../' . \basename($path)))();
|
||||
}
|
||||
|
||||
// Assign MIME type based on file extension (same as nginx/Apache) or fall back to given default otherwise.
|
||||
// Browsers are pretty good at figuring out the correct type if no charset attribute is given.
|
||||
$ext = \strtolower(\substr($path, \strrpos($path, '.') + 1));
|
||||
$headers = [
|
||||
'Content-Type' => $this->mimetypes[$ext] ?? $this->defaultMimetype
|
||||
];
|
||||
|
||||
$stat = @\stat($path);
|
||||
if ($stat !== false) {
|
||||
$headers['Last-Modified'] = \gmdate('D, d M Y H:i:s', $stat['mtime']) . ' GMT';
|
||||
|
||||
if ($request->getHeaderLine('If-Modified-Since') === $headers['Last-Modified']) {
|
||||
return new Response(Response::STATUS_NOT_MODIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(
|
||||
Response::STATUS_OK,
|
||||
$headers,
|
||||
\file_get_contents($path) // @phpstan-ignore-line TODO handle error if file can not be accessed
|
||||
);
|
||||
} else {
|
||||
return $this->errorHandler->requestNotFound();
|
||||
}
|
||||
}
|
||||
}
|
||||
67
vendor/clue/framework-x/src/Io/FiberHandler.php
vendored
67
vendor/clue/framework-x/src/Io/FiberHandler.php
vendored
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX\Io;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Fibers middleware handler to ensure each request is processed in a separate `Fiber`
|
||||
*
|
||||
* The `Fiber` class has been added in PHP 8.1+, so this middleware is only used
|
||||
* on PHP 8.1+. On supported PHP versions, this middleware is automatically
|
||||
* added to the list of middleware handlers, so there's no need to reference
|
||||
* this class in application code.
|
||||
*
|
||||
* @internal
|
||||
* @link https://framework-x.org/docs/async/fibers/
|
||||
*/
|
||||
class FiberHandler
|
||||
{
|
||||
/**
|
||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>|\Generator
|
||||
* Returns a `ResponseInterface` from the next request handler in the
|
||||
* chain. If the next request handler returns immediately, this method
|
||||
* will return immediately. If the next request handler suspends the
|
||||
* fiber (see `await()`), this method will return a `PromiseInterface`
|
||||
* that is fulfilled with a `ResponseInterface` when the fiber is
|
||||
* terminated successfully. If the next request handler returns a
|
||||
* promise, this method will return a promise that follows its
|
||||
* resolution. If the next request handler returns a Generator-based
|
||||
* coroutine, this method returns a `Generator`. This method never
|
||||
* throws or resolves a rejected promise. If the handler fails, it will
|
||||
* be turned into a valid error response before returning.
|
||||
* @throws void
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request, callable $next)
|
||||
{
|
||||
$deferred = null;
|
||||
$fiber = new \Fiber(function () use ($request, $next, &$deferred) {
|
||||
$response = $next($request);
|
||||
assert($response instanceof ResponseInterface || $response instanceof PromiseInterface || $response instanceof \Generator);
|
||||
|
||||
// if the next request handler returns immediately, the fiber can terminate immediately without using a Deferred
|
||||
// if the next request handler suspends the fiber, we only reach this point after resuming the fiber, so the code below will have assigned a Deferred
|
||||
/** @var ?Deferred<ResponseInterface> $deferred */
|
||||
if ($deferred !== null) {
|
||||
assert($response instanceof ResponseInterface);
|
||||
$deferred->resolve($response);
|
||||
}
|
||||
|
||||
return $response;
|
||||
});
|
||||
|
||||
/** @throws void because the next handler will always be an `ErrorHandler` */
|
||||
$fiber->start();
|
||||
if ($fiber->isTerminated()) {
|
||||
/** @throws void because fiber is known to have terminated successfully */
|
||||
/** @var ResponseInterface|PromiseInterface<ResponseInterface>|\Generator */
|
||||
return $fiber->getReturn();
|
||||
}
|
||||
|
||||
$deferred = new Deferred();
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
65
vendor/clue/framework-x/src/Io/HtmlHandler.php
vendored
65
vendor/clue/framework-x/src/Io/HtmlHandler.php
vendored
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX\Io;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\Http\Message\Response;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class HtmlHandler
|
||||
{
|
||||
public function statusResponse(int $statusCode, string $title, string $subtitle, string $info): ResponseInterface
|
||||
{
|
||||
$nonce = \base64_encode(\random_bytes(16));
|
||||
$html = <<<HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>$title</title>
|
||||
<style nonce="$nonce">
|
||||
body { display: grid; justify-content: center; align-items: center; grid-auto-rows: minmax(min-content, calc(100vh - 4em)); margin: 2em; font-family: ui-sans-serif, Arial, "Noto Sans", sans-serif; }
|
||||
@media (min-width: 700px) { main { display: grid; max-width: 700px; } }
|
||||
h1 { margin: 0 .5em 0 0; border-right: calc(2 * max(0px, min(100vw - 700px + 1px, 1px))) solid #e3e4e7; padding-right: .5em; color: #aebdcc; font-size: 3em; }
|
||||
strong { color: #111827; font-size: 3em; }
|
||||
p { margin: .5em 0 0 0; grid-column: 2; color: #6b7280; }
|
||||
code { padding: 0 .3em; background-color: #f5f6f9; } code span { padding: 0 .2em; border-radius: 3px; background-color: #0001; }
|
||||
a { color: inherit; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>$statusCode</h1>
|
||||
<strong>$subtitle</strong>
|
||||
$info</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
HTML;
|
||||
|
||||
return new Response(
|
||||
$statusCode,
|
||||
[
|
||||
'Content-Type' => 'text/html; charset=utf-8',
|
||||
'Content-Security-Policy' => "style-src 'nonce-$nonce'; img-src 'self'; default-src 'none'"
|
||||
],
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
public function escape(string $s): string
|
||||
{
|
||||
return (string) \preg_replace_callback(
|
||||
'/[\x00-\x1F]+/',
|
||||
function (array $match): string {
|
||||
return '<span>' . \addcslashes($match[0], "\x00..\xff") . '</span>';
|
||||
},
|
||||
(string) \preg_replace(
|
||||
'/(^| ) |(?: $)/',
|
||||
'$1 ',
|
||||
\htmlspecialchars($s, \ENT_NOQUOTES | \ENT_SUBSTITUTE | \ENT_DISALLOWED, 'utf-8')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX\Io;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class LogStreamHandler
|
||||
{
|
||||
/** @var ?resource */
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* @param string $path absolute log file path
|
||||
* @throws \InvalidArgumentException if given `$path` is not an absolute file path
|
||||
* @throws \RuntimeException if given `$path` can not be opened in append mode
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
if (\strpos($path, "\0") !== false || (\stripos($path, 'php://') !== 0 && !$this->isAbsolutePath($path))) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Unable to open log file "' . \addslashes($path) . '": Invalid path given'
|
||||
);
|
||||
}
|
||||
|
||||
$errstr = '';
|
||||
\set_error_handler(function (int $_, string $error) use (&$errstr): bool {
|
||||
// Match errstr from PHP's warning message.
|
||||
// fopen(/dev/not-a-valid-path): Failed to open stream: Permission denied
|
||||
$errstr = \preg_replace('#.*: #', '', $error);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$stream = \fopen($path, 'ae');
|
||||
|
||||
// try to fstat($stream) to see if this points to /dev/null (skip on Windows)
|
||||
// @codeCoverageIgnoreStart
|
||||
$stat = false;
|
||||
if ($stream !== false && \DIRECTORY_SEPARATOR !== '\\') {
|
||||
if (\strtolower($path) === 'php://output') {
|
||||
// php://output doesn't support stat, so assume php://output will go to php://stdout
|
||||
$stdout = \defined('STDOUT') ? \STDOUT : \fopen('php://stdout', 'w');
|
||||
if (\is_resource($stdout)) {
|
||||
$stat = \fstat($stdout);
|
||||
} else {
|
||||
// STDOUT can not be opened => assume piping to /dev/null
|
||||
$stream = null;
|
||||
}
|
||||
} else {
|
||||
$stat = \fstat($stream);
|
||||
}
|
||||
|
||||
// close stream if it points to /dev/null
|
||||
if ($stat !== false && $stat === \stat('/dev/null')) {
|
||||
$stream = null;
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
\restore_error_handler();
|
||||
|
||||
if ($stream === false) {
|
||||
throw new \RuntimeException(
|
||||
'Unable to open log file "' . $path . '": ' . $errstr
|
||||
);
|
||||
}
|
||||
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
public function isDevNull(): bool
|
||||
{
|
||||
return $this->stream === null;
|
||||
}
|
||||
|
||||
public function log(string $message): void
|
||||
{
|
||||
// nothing to do if we're writing to /dev/null
|
||||
if ($this->stream === null) {
|
||||
return; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$time = \microtime(true);
|
||||
$prefix = \date('Y-m-d H:i:s', (int) $time) . \sprintf('.%03d ', (int) (($time - (int) $time) * 1e3));
|
||||
|
||||
$ret = \fwrite($this->stream, $prefix . $message . \PHP_EOL);
|
||||
assert(\is_int($ret));
|
||||
}
|
||||
|
||||
private function isAbsolutePath(string $path): bool
|
||||
{
|
||||
return \DIRECTORY_SEPARATOR !== '\\' ? \substr($path, 0, 1) === '/' : (bool) \preg_match('#^[A-Z]:[/\\\\]#i', $path);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX\Io;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MiddlewareHandler
|
||||
{
|
||||
/** @var list<callable> $handlers */
|
||||
private $handlers;
|
||||
|
||||
/** @param list<callable> $handlers */
|
||||
public function __construct(array $handlers)
|
||||
{
|
||||
assert(count($handlers) >= 2);
|
||||
|
||||
$this->handlers = $handlers;
|
||||
}
|
||||
|
||||
/** @return mixed */
|
||||
public function __invoke(ServerRequestInterface $request)
|
||||
{
|
||||
return $this->call($request, 0);
|
||||
}
|
||||
|
||||
/** @return mixed */
|
||||
private function call(ServerRequestInterface $request, int $position)
|
||||
{
|
||||
if (!isset($this->handlers[$position + 2])) {
|
||||
return $this->handlers[$position]($request, $this->handlers[$position + 1]);
|
||||
}
|
||||
|
||||
return $this->handlers[$position]($request, function (ServerRequestInterface $request) use ($position) {
|
||||
return $this->call($request, $position + 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX\Io;
|
||||
|
||||
use React\EventLoop\Loop;
|
||||
use React\Http\HttpServer;
|
||||
use React\Socket\SocketServer;
|
||||
|
||||
/**
|
||||
* [Internal] Powerful reactive request handler built on top of ReactPHP.
|
||||
*
|
||||
* This is where the magic happens: The main `App` uses this class to run
|
||||
* ReactPHP's efficient HTTP server to handle incoming HTTP requests when
|
||||
* executed on the command line (CLI). ReactPHP's lightweight socket server can
|
||||
* listen for a large number of concurrent connections and process multiple
|
||||
* incoming connections simultaneously. The long-running server process will
|
||||
* continue to run until it is interrupted by a signal.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually have
|
||||
* to care about. See also the `App` and `SapiHandler` for more details.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ReactiveHandler
|
||||
{
|
||||
/** @var LogStreamHandler */
|
||||
private $logger;
|
||||
|
||||
/** @var string */
|
||||
private $listenAddress;
|
||||
|
||||
public function __construct(?string $listenAddress)
|
||||
{
|
||||
/** @throws void */
|
||||
$this->logger = new LogStreamHandler('php://output');
|
||||
$this->listenAddress = $listenAddress ?? '127.0.0.1:8080';
|
||||
}
|
||||
|
||||
public function run(callable $handler): void
|
||||
{
|
||||
$socket = new SocketServer($this->listenAddress);
|
||||
|
||||
// create HTTP server, automatically start new fiber for each request on PHP 8.1+
|
||||
$http = new HttpServer(...(\PHP_VERSION_ID >= 80100 ? [new FiberHandler(), $handler] : [$handler]));
|
||||
$http->listen($socket);
|
||||
|
||||
$logger = $this->logger;
|
||||
$logger->log('Listening on ' . \str_replace('tcp:', 'http:', (string) $socket->getAddress()));
|
||||
|
||||
$http->on('error', static function (\Exception $e) use ($logger): void {
|
||||
$logger->log('HTTP error: ' . $e->getMessage());
|
||||
});
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
try {
|
||||
Loop::addSignal(\defined('SIGINT') ? \SIGINT : 2, $f1 = static function () use ($socket, $logger): void {
|
||||
if (\PHP_VERSION_ID >= 70200 && \stream_isatty(\STDIN)) {
|
||||
echo "\r";
|
||||
}
|
||||
$logger->log('Received SIGINT, stopping loop');
|
||||
|
||||
$socket->close();
|
||||
Loop::stop();
|
||||
});
|
||||
Loop::addSignal(\defined('SIGTERM') ? \SIGTERM : 15, $f2 = static function () use ($socket, $logger): void {
|
||||
$logger->log('Received SIGTERM, stopping loop');
|
||||
|
||||
$socket->close();
|
||||
Loop::stop();
|
||||
});
|
||||
} catch (\BadMethodCallException $e) {
|
||||
$logger->log('Notice: No signal handler support, installing ext-ev or ext-pcntl recommended for production use.');
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
do {
|
||||
Loop::run();
|
||||
|
||||
if ($socket->getAddress() !== null) {
|
||||
// Fiber compatibility mode for PHP < 8.1: Restart loop as long as socket is available
|
||||
$logger->log('Warning: Loop restarted. Upgrade to react/async v4 recommended for production use.');
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
// remove signal handlers when loop stops (if registered)
|
||||
Loop::removeSignal(\defined('SIGINT') ? \SIGINT : 2, $f1 ?? 'printf');
|
||||
Loop::removeSignal(\defined('SIGTERM') ? \SIGTERM : 15, $f2 ?? 'printf');
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX\Io;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\Http\Message\Response;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RedirectHandler
|
||||
{
|
||||
/** @var string */
|
||||
private $target;
|
||||
|
||||
/** @var int */
|
||||
private $code;
|
||||
|
||||
/** @var string */
|
||||
private $reason;
|
||||
|
||||
/** @var HtmlHandler */
|
||||
private $html;
|
||||
|
||||
public function __construct(string $target, int $redirectStatusCode = Response::STATUS_FOUND)
|
||||
{
|
||||
if ($redirectStatusCode < 300 || $redirectStatusCode === Response::STATUS_NOT_MODIFIED || $redirectStatusCode >= 400) {
|
||||
throw new \InvalidArgumentException('Invalid redirect status code given');
|
||||
}
|
||||
|
||||
$this->target = $target;
|
||||
$this->code = $redirectStatusCode;
|
||||
$this->reason = \ucwords((new Response($redirectStatusCode))->getReasonPhrase()) ?: 'Redirect';
|
||||
$this->html = new HtmlHandler();
|
||||
}
|
||||
|
||||
public function __invoke(): ResponseInterface
|
||||
{
|
||||
$url = $this->html->escape($this->target);
|
||||
|
||||
return $this->html->statusResponse(
|
||||
$this->code,
|
||||
'Redirecting to ' . $url,
|
||||
$this->reason,
|
||||
"<p>Redirecting to <a href=\"$url\"><code>$url</code></a>...</p>\n"
|
||||
)->withHeader('Location', $this->target);
|
||||
}
|
||||
}
|
||||
116
vendor/clue/framework-x/src/Io/RouteHandler.php
vendored
116
vendor/clue/framework-x/src/Io/RouteHandler.php
vendored
@@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX\Io;
|
||||
|
||||
use FastRoute\DataGenerator\GroupCountBased as RouteGenerator;
|
||||
use FastRoute\Dispatcher\GroupCountBased as RouteDispatcher;
|
||||
use FastRoute\RouteCollector;
|
||||
use FastRoute\RouteParser\Std as RouteParser;
|
||||
use FrameworkX\AccessLogHandler;
|
||||
use FrameworkX\Container;
|
||||
use FrameworkX\ErrorHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RouteHandler
|
||||
{
|
||||
/** @var RouteCollector */
|
||||
private $routeCollector;
|
||||
|
||||
/** @var ?RouteDispatcher */
|
||||
private $routeDispatcher;
|
||||
|
||||
/** @var ErrorHandler */
|
||||
private $errorHandler;
|
||||
|
||||
/** @var Container */
|
||||
private $container;
|
||||
|
||||
public function __construct(Container $container = null)
|
||||
{
|
||||
$this->routeCollector = new RouteCollector(new RouteParser(), new RouteGenerator());
|
||||
$this->errorHandler = new ErrorHandler();
|
||||
$this->container = $container ?? new Container();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $methods
|
||||
* @param string $route
|
||||
* @param callable|class-string $handler
|
||||
* @param callable|class-string ...$handlers
|
||||
*/
|
||||
public function map(array $methods, string $route, $handler, ...$handlers): void
|
||||
{
|
||||
if ($handlers) {
|
||||
\array_unshift($handlers, $handler);
|
||||
\end($handlers);
|
||||
} else {
|
||||
$handlers = [$handler];
|
||||
}
|
||||
|
||||
$last = key($handlers);
|
||||
$container = $this->container;
|
||||
foreach ($handlers as $i => $handler) {
|
||||
if ($handler instanceof Container && $i !== $last) {
|
||||
$container = $handler;
|
||||
unset($handlers[$i]);
|
||||
} elseif ($handler instanceof AccessLogHandler || $handler === AccessLogHandler::class) {
|
||||
throw new \TypeError('AccessLogHandler may currently only be passed as a global middleware');
|
||||
} elseif (!\is_callable($handler)) {
|
||||
$handlers[$i] = $container->callable($handler);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var non-empty-array<callable> $handlers */
|
||||
$handler = \count($handlers) > 1 ? new MiddlewareHandler(array_values($handlers)) : \reset($handlers);
|
||||
$this->routeDispatcher = null;
|
||||
$this->routeCollector->addRoute($methods, $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface|PromiseInterface<ResponseInterface>|\Generator
|
||||
*/
|
||||
public function __invoke(ServerRequestInterface $request)
|
||||
{
|
||||
$target = $request->getRequestTarget();
|
||||
if ($target[0] !== '/' && $target !== '*') {
|
||||
return $this->errorHandler->requestProxyUnsupported();
|
||||
} elseif ($target !== '*') {
|
||||
$target = $request->getUri()->getPath();
|
||||
}
|
||||
|
||||
if ($this->routeDispatcher === null) {
|
||||
$this->routeDispatcher = new RouteDispatcher($this->routeCollector->getData());
|
||||
}
|
||||
|
||||
$routeInfo = $this->routeDispatcher->dispatch($request->getMethod(), $target);
|
||||
assert(\is_array($routeInfo) && isset($routeInfo[0]));
|
||||
|
||||
// happy path: matching route found, assign route attributes and invoke request handler
|
||||
if ($routeInfo[0] === \FastRoute\Dispatcher::FOUND) {
|
||||
$handler = $routeInfo[1];
|
||||
$vars = $routeInfo[2];
|
||||
|
||||
foreach ($vars as $key => $value) {
|
||||
$request = $request->withAttribute($key, rawurldecode($value));
|
||||
}
|
||||
|
||||
return $handler($request);
|
||||
}
|
||||
|
||||
// no matching route found: report error `404 Not Found`
|
||||
if ($routeInfo[0] === \FastRoute\Dispatcher::NOT_FOUND) {
|
||||
return $this->errorHandler->requestNotFound();
|
||||
}
|
||||
|
||||
// unexpected request method for route: report error `405 Method Not Allowed`
|
||||
assert($routeInfo[0] === \FastRoute\Dispatcher::METHOD_NOT_ALLOWED);
|
||||
assert(\is_array($routeInfo[1]) && \count($routeInfo[1]) > 0);
|
||||
|
||||
return $this->errorHandler->requestMethodNotAllowed($routeInfo[1]);
|
||||
}
|
||||
}
|
||||
164
vendor/clue/framework-x/src/Io/SapiHandler.php
vendored
164
vendor/clue/framework-x/src/Io/SapiHandler.php
vendored
@@ -1,164 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FrameworkX\Io;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use React\EventLoop\Loop;
|
||||
use React\Http\Message\Response;
|
||||
use React\Http\Message\ServerRequest;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Request handler for traditional PHP SAPIs.
|
||||
*
|
||||
* This request handler will be used when executed behind traditional PHP SAPIs
|
||||
* (PHP-FPM, FastCGI, Apache, etc.). It will handle a single request and run
|
||||
* until a single response is sent. This is particularly useful because it
|
||||
* allows you to run the exact same app in any environment.
|
||||
*
|
||||
* Note that this is an internal class only and nothing you should usually have
|
||||
* to care about. See also the `App` and `ReactiveHandler` for more details.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SapiHandler
|
||||
{
|
||||
public function run(callable $handler): void
|
||||
{
|
||||
$request = $this->requestFromGlobals();
|
||||
|
||||
$response = $handler($request);
|
||||
|
||||
if ($response instanceof ResponseInterface) {
|
||||
$this->sendResponse($response);
|
||||
} elseif ($response instanceof PromiseInterface) {
|
||||
/** @var PromiseInterface<ResponseInterface> $response */
|
||||
$response->then(function (ResponseInterface $response): void {
|
||||
$this->sendResponse($response);
|
||||
});
|
||||
}
|
||||
|
||||
Loop::run();
|
||||
}
|
||||
|
||||
public function requestFromGlobals(): ServerRequestInterface
|
||||
{
|
||||
$host = null;
|
||||
$headers = array();
|
||||
// @codeCoverageIgnoreStart
|
||||
if (\function_exists('getallheaders')) {
|
||||
$headers = \getallheaders();
|
||||
$host = \array_change_key_case($headers, \CASE_LOWER)['host'] ?? null;
|
||||
} else {
|
||||
foreach ($_SERVER as $key => $value) {
|
||||
if (\strpos($key, 'HTTP_') === 0) {
|
||||
$key = str_replace(' ', '-', \ucwords(\strtolower(\str_replace('_', ' ', \substr($key, 5)))));
|
||||
$headers[$key] = $value;
|
||||
|
||||
if ($host === null && $key === 'Host') {
|
||||
$host = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$target = ($_SERVER['REQUEST_URI'] ?? '/');
|
||||
$url = $target;
|
||||
if (($target[0] ?? '/') === '/' || $target === '*') {
|
||||
$url = (($_SERVER['HTTPS'] ?? null) === 'on' ? 'https://' : 'http://') . ($host ?? 'localhost') . ($target === '*' ? '' : $target);
|
||||
}
|
||||
|
||||
$body = file_get_contents('php://input');
|
||||
assert(\is_string($body));
|
||||
|
||||
$request = new ServerRequest(
|
||||
$_SERVER['REQUEST_METHOD'] ?? 'GET',
|
||||
$url,
|
||||
$headers,
|
||||
$body,
|
||||
substr($_SERVER['SERVER_PROTOCOL'] ?? 'http/1.1', 5),
|
||||
$_SERVER
|
||||
);
|
||||
if ($host === null) {
|
||||
$request = $request->withoutHeader('Host');
|
||||
}
|
||||
if (isset($target[0]) && $target[0] !== '/') {
|
||||
$request = $request->withRequestTarget($target);
|
||||
}
|
||||
$request = $request->withParsedBody($_POST);
|
||||
|
||||
// Content-Length / Content-Type are special <3
|
||||
if ($request->getHeaderLine('Content-Length') === '') {
|
||||
$request = $request->withoutHeader('Content-Length');
|
||||
}
|
||||
if ($request->getHeaderLine('Content-Type') === '' && !isset($_SERVER['HTTP_CONTENT_TYPE'])) {
|
||||
$request = $request->withoutHeader('Content-Type');
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function sendResponse(ResponseInterface $response): void
|
||||
{
|
||||
$status = $response->getStatusCode();
|
||||
$body = $response->getBody();
|
||||
|
||||
if ($status === Response::STATUS_NO_CONTENT) {
|
||||
// `204 No Content` MUST NOT include "Content-Length" response header
|
||||
$response = $response->withoutHeader('Content-Length');
|
||||
} elseif (!$response->hasHeader('Content-Length') && $body->getSize() !== null && ($status !== Response::STATUS_NOT_MODIFIED || $body->getSize() !== 0)) {
|
||||
// automatically assign "Content-Length" response header if known and not already present
|
||||
$response = $response->withHeader('Content-Length', (string) $body->getSize());
|
||||
}
|
||||
|
||||
// remove default "Content-Type" header set by PHP (default_mimetype)
|
||||
if (!$response->hasHeader('Content-Type')) {
|
||||
header('Content-Type:');
|
||||
header_remove('Content-Type');
|
||||
}
|
||||
|
||||
// send all headers without applying default "; charset=utf-8" set by PHP (default_charset)
|
||||
$old = ini_get('default_charset');
|
||||
assert(\is_string($old));
|
||||
ini_set('default_charset', '');
|
||||
foreach ($response->getHeaders() as $name => $values) {
|
||||
foreach ($values as $value) {
|
||||
header($name . ': ' . $value, false);
|
||||
}
|
||||
}
|
||||
ini_set('default_charset', $old);
|
||||
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' ' . $status . ' ' . $response->getReasonPhrase());
|
||||
|
||||
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'HEAD' || $status === Response::STATUS_NO_CONTENT || $status === Response::STATUS_NOT_MODIFIED) {
|
||||
$body->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
// try to disable nginx buffering (http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering)
|
||||
if (isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') === 0) {
|
||||
header('X-Accel-Buffering: no');
|
||||
}
|
||||
|
||||
// clear output buffer to show streaming output (default in cli-server)
|
||||
if (\PHP_SAPI === 'cli-server') {
|
||||
\ob_end_flush(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// flush data whenever stream reports one data chunk
|
||||
$body->on('data', function ($chunk) {
|
||||
echo $chunk;
|
||||
flush();
|
||||
});
|
||||
} else {
|
||||
echo $body;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
vendor/fig/http-message-util/.gitignore
vendored
1
vendor/fig/http-message-util/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
vendor/
|
||||
147
vendor/fig/http-message-util/CHANGELOG.md
vendored
147
vendor/fig/http-message-util/CHANGELOG.md
vendored
@@ -1,147 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file, in reverse chronological order by release.
|
||||
|
||||
## 1.1.5 - 2020-11-24
|
||||
|
||||
### Added
|
||||
|
||||
- [#19](https://github.com/php-fig/http-message-util/pull/19) adds support for PHP 8.
|
||||
|
||||
### Changed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 1.1.4 - 2020-02-05
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Changed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- [#15](https://github.com/php-fig/http-message-util/pull/15) removes the dependency on psr/http-message, as it is not technically necessary for usage of this package.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 1.1.3 - 2018-11-19
|
||||
|
||||
### Added
|
||||
|
||||
- [#10](https://github.com/php-fig/http-message-util/pull/10) adds the constants `StatusCodeInterface::STATUS_EARLY_HINTS` (103) and
|
||||
`StatusCodeInterface::STATUS_TOO_EARLY` (425).
|
||||
|
||||
### Changed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 1.1.2 - 2017-02-09
|
||||
|
||||
### Added
|
||||
|
||||
- [#4](https://github.com/php-fig/http-message-util/pull/4) adds the constant
|
||||
`StatusCodeInterface::STATUS_MISDIRECTED_REQUEST` (421).
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 1.1.1 - 2017-02-06
|
||||
|
||||
### Added
|
||||
|
||||
- [#3](https://github.com/php-fig/http-message-util/pull/3) adds the constant
|
||||
`StatusCodeInterface::STATUS_IM_A_TEAPOT` (418).
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 1.1.0 - 2016-09-19
|
||||
|
||||
### Added
|
||||
|
||||
- [#1](https://github.com/php-fig/http-message-util/pull/1) adds
|
||||
`Fig\Http\Message\StatusCodeInterface`, with constants named after common
|
||||
status reason phrases, with values indicating the status codes themselves.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 1.0.0 - 2017-08-05
|
||||
|
||||
### Added
|
||||
|
||||
- Adds `Fig\Http\Message\RequestMethodInterface`, with constants covering the
|
||||
most common HTTP request methods as specified by the IETF.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
19
vendor/fig/http-message-util/LICENSE
vendored
19
vendor/fig/http-message-util/LICENSE
vendored
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2016 PHP Framework Interoperability Group
|
||||
|
||||
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
vendor/fig/http-message-util/README.md
vendored
17
vendor/fig/http-message-util/README.md
vendored
@@ -1,17 +0,0 @@
|
||||
# PSR Http Message Util
|
||||
|
||||
This repository holds utility classes and constants to facilitate common
|
||||
operations of [PSR-7](https://www.php-fig.org/psr/psr-7/); the primary purpose is
|
||||
to provide constants for referring to request methods, response status codes and
|
||||
messages, and potentially common headers.
|
||||
|
||||
Implementation of PSR-7 interfaces is **not** within the scope of this package.
|
||||
|
||||
## Installation
|
||||
|
||||
Install by adding the package as a [Composer](https://getcomposer.org)
|
||||
requirement:
|
||||
|
||||
```bash
|
||||
$ composer require fig/http-message-util
|
||||
```
|
||||
28
vendor/fig/http-message-util/composer.json
vendored
28
vendor/fig/http-message-util/composer.json
vendored
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "fig/http-message-util",
|
||||
"description": "Utility classes and constants for use with PSR-7 (psr/http-message)",
|
||||
"keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.3 || ^7.0 || ^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/http-message": "The package containing the PSR-7 interfaces"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fig\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Fig\Http\Message;
|
||||
|
||||
/**
|
||||
* Defines constants for common HTTP request methods.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* <code>
|
||||
* class RequestFactory implements RequestMethodInterface
|
||||
* {
|
||||
* public static function factory(
|
||||
* $uri = '/',
|
||||
* $method = self::METHOD_GET,
|
||||
* $data = []
|
||||
* ) {
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
interface RequestMethodInterface
|
||||
{
|
||||
const METHOD_HEAD = 'HEAD';
|
||||
const METHOD_GET = 'GET';
|
||||
const METHOD_POST = 'POST';
|
||||
const METHOD_PUT = 'PUT';
|
||||
const METHOD_PATCH = 'PATCH';
|
||||
const METHOD_DELETE = 'DELETE';
|
||||
const METHOD_PURGE = 'PURGE';
|
||||
const METHOD_OPTIONS = 'OPTIONS';
|
||||
const METHOD_TRACE = 'TRACE';
|
||||
const METHOD_CONNECT = 'CONNECT';
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Fig\Http\Message;
|
||||
|
||||
/**
|
||||
* Defines constants for common HTTP status code.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc2295#section-8.1
|
||||
* @see https://tools.ietf.org/html/rfc2324#section-2.3
|
||||
* @see https://tools.ietf.org/html/rfc2518#section-9.7
|
||||
* @see https://tools.ietf.org/html/rfc2774#section-7
|
||||
* @see https://tools.ietf.org/html/rfc3229#section-10.4
|
||||
* @see https://tools.ietf.org/html/rfc4918#section-11
|
||||
* @see https://tools.ietf.org/html/rfc5842#section-7.1
|
||||
* @see https://tools.ietf.org/html/rfc5842#section-7.2
|
||||
* @see https://tools.ietf.org/html/rfc6585#section-3
|
||||
* @see https://tools.ietf.org/html/rfc6585#section-4
|
||||
* @see https://tools.ietf.org/html/rfc6585#section-5
|
||||
* @see https://tools.ietf.org/html/rfc6585#section-6
|
||||
* @see https://tools.ietf.org/html/rfc7231#section-6
|
||||
* @see https://tools.ietf.org/html/rfc7238#section-3
|
||||
* @see https://tools.ietf.org/html/rfc7725#section-3
|
||||
* @see https://tools.ietf.org/html/rfc7540#section-9.1.2
|
||||
* @see https://tools.ietf.org/html/rfc8297#section-2
|
||||
* @see https://tools.ietf.org/html/rfc8470#section-7
|
||||
* Usage:
|
||||
*
|
||||
* <code>
|
||||
* class ResponseFactory implements StatusCodeInterface
|
||||
* {
|
||||
* public function createResponse($code = self::STATUS_OK)
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
interface StatusCodeInterface
|
||||
{
|
||||
// Informational 1xx
|
||||
const STATUS_CONTINUE = 100;
|
||||
const STATUS_SWITCHING_PROTOCOLS = 101;
|
||||
const STATUS_PROCESSING = 102;
|
||||
const STATUS_EARLY_HINTS = 103;
|
||||
// Successful 2xx
|
||||
const STATUS_OK = 200;
|
||||
const STATUS_CREATED = 201;
|
||||
const STATUS_ACCEPTED = 202;
|
||||
const STATUS_NON_AUTHORITATIVE_INFORMATION = 203;
|
||||
const STATUS_NO_CONTENT = 204;
|
||||
const STATUS_RESET_CONTENT = 205;
|
||||
const STATUS_PARTIAL_CONTENT = 206;
|
||||
const STATUS_MULTI_STATUS = 207;
|
||||
const STATUS_ALREADY_REPORTED = 208;
|
||||
const STATUS_IM_USED = 226;
|
||||
// Redirection 3xx
|
||||
const STATUS_MULTIPLE_CHOICES = 300;
|
||||
const STATUS_MOVED_PERMANENTLY = 301;
|
||||
const STATUS_FOUND = 302;
|
||||
const STATUS_SEE_OTHER = 303;
|
||||
const STATUS_NOT_MODIFIED = 304;
|
||||
const STATUS_USE_PROXY = 305;
|
||||
const STATUS_RESERVED = 306;
|
||||
const STATUS_TEMPORARY_REDIRECT = 307;
|
||||
const STATUS_PERMANENT_REDIRECT = 308;
|
||||
// Client Errors 4xx
|
||||
const STATUS_BAD_REQUEST = 400;
|
||||
const STATUS_UNAUTHORIZED = 401;
|
||||
const STATUS_PAYMENT_REQUIRED = 402;
|
||||
const STATUS_FORBIDDEN = 403;
|
||||
const STATUS_NOT_FOUND = 404;
|
||||
const STATUS_METHOD_NOT_ALLOWED = 405;
|
||||
const STATUS_NOT_ACCEPTABLE = 406;
|
||||
const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407;
|
||||
const STATUS_REQUEST_TIMEOUT = 408;
|
||||
const STATUS_CONFLICT = 409;
|
||||
const STATUS_GONE = 410;
|
||||
const STATUS_LENGTH_REQUIRED = 411;
|
||||
const STATUS_PRECONDITION_FAILED = 412;
|
||||
const STATUS_PAYLOAD_TOO_LARGE = 413;
|
||||
const STATUS_URI_TOO_LONG = 414;
|
||||
const STATUS_UNSUPPORTED_MEDIA_TYPE = 415;
|
||||
const STATUS_RANGE_NOT_SATISFIABLE = 416;
|
||||
const STATUS_EXPECTATION_FAILED = 417;
|
||||
const STATUS_IM_A_TEAPOT = 418;
|
||||
const STATUS_MISDIRECTED_REQUEST = 421;
|
||||
const STATUS_UNPROCESSABLE_ENTITY = 422;
|
||||
const STATUS_LOCKED = 423;
|
||||
const STATUS_FAILED_DEPENDENCY = 424;
|
||||
const STATUS_TOO_EARLY = 425;
|
||||
const STATUS_UPGRADE_REQUIRED = 426;
|
||||
const STATUS_PRECONDITION_REQUIRED = 428;
|
||||
const STATUS_TOO_MANY_REQUESTS = 429;
|
||||
const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
|
||||
const STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
|
||||
// Server Errors 5xx
|
||||
const STATUS_INTERNAL_SERVER_ERROR = 500;
|
||||
const STATUS_NOT_IMPLEMENTED = 501;
|
||||
const STATUS_BAD_GATEWAY = 502;
|
||||
const STATUS_SERVICE_UNAVAILABLE = 503;
|
||||
const STATUS_GATEWAY_TIMEOUT = 504;
|
||||
const STATUS_VERSION_NOT_SUPPORTED = 505;
|
||||
const STATUS_VARIANT_ALSO_NEGOTIATES = 506;
|
||||
const STATUS_INSUFFICIENT_STORAGE = 507;
|
||||
const STATUS_LOOP_DETECTED = 508;
|
||||
const STATUS_NOT_EXTENDED = 510;
|
||||
const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511;
|
||||
}
|
||||
5
vendor/nikic/fast-route/.gitignore
vendored
5
vendor/nikic/fast-route/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
/vendor/
|
||||
.idea/
|
||||
|
||||
# ignore lock file since we have no extra dependencies
|
||||
composer.lock
|
||||
1
vendor/nikic/fast-route/.hhconfig
vendored
1
vendor/nikic/fast-route/.hhconfig
vendored
@@ -1 +0,0 @@
|
||||
assume_php=false
|
||||
20
vendor/nikic/fast-route/.travis.yml
vendored
20
vendor/nikic/fast-route/.travis.yml
vendored
@@ -1,20 +0,0 @@
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- hhvm
|
||||
|
||||
script:
|
||||
- ./vendor/bin/phpunit
|
||||
|
||||
before_install:
|
||||
- travis_retry composer self-update
|
||||
|
||||
install:
|
||||
- composer install
|
||||
126
vendor/nikic/fast-route/FastRoute.hhi
vendored
126
vendor/nikic/fast-route/FastRoute.hhi
vendored
@@ -1,126 +0,0 @@
|
||||
<?hh // decl
|
||||
|
||||
namespace FastRoute {
|
||||
class BadRouteException extends \LogicException {
|
||||
}
|
||||
|
||||
interface RouteParser {
|
||||
public function parse(string $route): array<array>;
|
||||
}
|
||||
|
||||
class RouteCollector {
|
||||
public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator);
|
||||
public function addRoute(mixed $httpMethod, string $route, mixed $handler): void;
|
||||
public function getData(): array;
|
||||
}
|
||||
|
||||
class Route {
|
||||
public function __construct(string $httpMethod, mixed $handler, string $regex, array $variables);
|
||||
public function matches(string $str): bool;
|
||||
}
|
||||
|
||||
interface DataGenerator {
|
||||
public function addRoute(string $httpMethod, array $routeData, mixed $handler);
|
||||
public function getData(): array;
|
||||
}
|
||||
|
||||
interface Dispatcher {
|
||||
const int NOT_FOUND = 0;
|
||||
const int FOUND = 1;
|
||||
const int METHOD_NOT_ALLOWED = 2;
|
||||
public function dispatch(string $httpMethod, string $uri): array;
|
||||
}
|
||||
|
||||
function simpleDispatcher(
|
||||
(function(RouteCollector): void) $routeDefinitionCallback,
|
||||
shape(
|
||||
?'routeParser' => classname<RouteParser>,
|
||||
?'dataGenerator' => classname<DataGenerator>,
|
||||
?'dispatcher' => classname<Dispatcher>,
|
||||
?'routeCollector' => classname<RouteCollector>,
|
||||
) $options = shape()): Dispatcher;
|
||||
|
||||
function cachedDispatcher(
|
||||
(function(RouteCollector): void) $routeDefinitionCallback,
|
||||
shape(
|
||||
?'routeParser' => classname<RouteParser>,
|
||||
?'dataGenerator' => classname<DataGenerator>,
|
||||
?'dispatcher' => classname<Dispatcher>,
|
||||
?'routeCollector' => classname<RouteCollector>,
|
||||
?'cacheDisabled' => bool,
|
||||
?'cacheFile' => string,
|
||||
) $options = shape()): Dispatcher;
|
||||
}
|
||||
|
||||
namespace FastRoute\DataGenerator {
|
||||
abstract class RegexBasedAbstract implements \FastRoute\DataGenerator {
|
||||
protected abstract function getApproxChunkSize();
|
||||
protected abstract function processChunk($regexToRoutesMap);
|
||||
|
||||
public function addRoute(string $httpMethod, array $routeData, mixed $handler): void;
|
||||
public function getData(): array;
|
||||
}
|
||||
|
||||
class CharCountBased extends RegexBasedAbstract {
|
||||
protected function getApproxChunkSize(): int;
|
||||
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
|
||||
}
|
||||
|
||||
class GroupCountBased extends RegexBasedAbstract {
|
||||
protected function getApproxChunkSize(): int;
|
||||
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
|
||||
}
|
||||
|
||||
class GroupPosBased extends RegexBasedAbstract {
|
||||
protected function getApproxChunkSize(): int;
|
||||
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
|
||||
}
|
||||
|
||||
class MarkBased extends RegexBasedAbstract {
|
||||
protected function getApproxChunkSize(): int;
|
||||
protected function processChunk(array<string, string> $regexToRoutesMap): array<string, mixed>;
|
||||
}
|
||||
}
|
||||
|
||||
namespace FastRoute\Dispatcher {
|
||||
abstract class RegexBasedAbstract implements \FastRoute\Dispatcher {
|
||||
protected abstract function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
||||
|
||||
public function dispatch(string $httpMethod, string $uri): array;
|
||||
}
|
||||
|
||||
class GroupPosBased extends RegexBasedAbstract {
|
||||
public function __construct(array $data);
|
||||
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
||||
}
|
||||
|
||||
class GroupCountBased extends RegexBasedAbstract {
|
||||
public function __construct(array $data);
|
||||
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
||||
}
|
||||
|
||||
class CharCountBased extends RegexBasedAbstract {
|
||||
public function __construct(array $data);
|
||||
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
||||
}
|
||||
|
||||
class MarkBased extends RegexBasedAbstract {
|
||||
public function __construct(array $data);
|
||||
protected function dispatchVariableRoute(array<array> $routeData, string $uri): array;
|
||||
}
|
||||
}
|
||||
|
||||
namespace FastRoute\RouteParser {
|
||||
class Std implements \FastRoute\RouteParser {
|
||||
const string VARIABLE_REGEX = <<<'REGEX'
|
||||
\{
|
||||
\s* ([a-zA-Z][a-zA-Z0-9_]*) \s*
|
||||
(?:
|
||||
: \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*)
|
||||
)?
|
||||
\}
|
||||
REGEX;
|
||||
const string DEFAULT_DISPATCH_REGEX = '[^/]+';
|
||||
public function parse(string $route): array<array>;
|
||||
}
|
||||
}
|
||||
31
vendor/nikic/fast-route/LICENSE
vendored
31
vendor/nikic/fast-route/LICENSE
vendored
@@ -1,31 +0,0 @@
|
||||
Copyright (c) 2013 by Nikita Popov.
|
||||
|
||||
Some 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.
|
||||
|
||||
* The names of the contributors may not 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.
|
||||
313
vendor/nikic/fast-route/README.md
vendored
313
vendor/nikic/fast-route/README.md
vendored
@@ -1,313 +0,0 @@
|
||||
FastRoute - Fast request router for PHP
|
||||
=======================================
|
||||
|
||||
This library provides a fast implementation of a regular expression based router. [Blog post explaining how the
|
||||
implementation works and why it is fast.][blog_post]
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
To install with composer:
|
||||
|
||||
```sh
|
||||
composer require nikic/fast-route
|
||||
```
|
||||
|
||||
Requires PHP 5.4 or newer.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Here's a basic usage example:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
require '/path/to/vendor/autoload.php';
|
||||
|
||||
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
|
||||
$r->addRoute('GET', '/users', 'get_all_users_handler');
|
||||
// {id} must be a number (\d+)
|
||||
$r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler');
|
||||
// The /{title} suffix is optional
|
||||
$r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler');
|
||||
});
|
||||
|
||||
// Fetch method and URI from somewhere
|
||||
$httpMethod = $_SERVER['REQUEST_METHOD'];
|
||||
$uri = $_SERVER['REQUEST_URI'];
|
||||
|
||||
// Strip query string (?foo=bar) and decode URI
|
||||
if (false !== $pos = strpos($uri, '?')) {
|
||||
$uri = substr($uri, 0, $pos);
|
||||
}
|
||||
$uri = rawurldecode($uri);
|
||||
|
||||
$routeInfo = $dispatcher->dispatch($httpMethod, $uri);
|
||||
switch ($routeInfo[0]) {
|
||||
case FastRoute\Dispatcher::NOT_FOUND:
|
||||
// ... 404 Not Found
|
||||
break;
|
||||
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
|
||||
$allowedMethods = $routeInfo[1];
|
||||
// ... 405 Method Not Allowed
|
||||
break;
|
||||
case FastRoute\Dispatcher::FOUND:
|
||||
$handler = $routeInfo[1];
|
||||
$vars = $routeInfo[2];
|
||||
// ... call $handler with $vars
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
### Defining routes
|
||||
|
||||
The routes are defined by calling the `FastRoute\simpleDispatcher()` function, which accepts
|
||||
a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling
|
||||
`addRoute()` on the collector instance:
|
||||
|
||||
```php
|
||||
$r->addRoute($method, $routePattern, $handler);
|
||||
```
|
||||
|
||||
The `$method` is an uppercase HTTP method string for which a certain route should match. It
|
||||
is possible to specify multiple valid methods using an array:
|
||||
|
||||
```php
|
||||
// These two calls
|
||||
$r->addRoute('GET', '/test', 'handler');
|
||||
$r->addRoute('POST', '/test', 'handler');
|
||||
// Are equivalent to this one call
|
||||
$r->addRoute(['GET', 'POST'], '/test', 'handler');
|
||||
```
|
||||
|
||||
By default the `$routePattern` uses a syntax where `{foo}` specifies a placeholder with name `foo`
|
||||
and matching the regex `[^/]+`. To adjust the pattern the placeholder matches, you can specify
|
||||
a custom pattern by writing `{bar:[0-9]+}`. Some examples:
|
||||
|
||||
```php
|
||||
// Matches /user/42, but not /user/xyz
|
||||
$r->addRoute('GET', '/user/{id:\d+}', 'handler');
|
||||
|
||||
// Matches /user/foobar, but not /user/foo/bar
|
||||
$r->addRoute('GET', '/user/{name}', 'handler');
|
||||
|
||||
// Matches /user/foo/bar as well
|
||||
$r->addRoute('GET', '/user/{name:.+}', 'handler');
|
||||
```
|
||||
|
||||
Custom patterns for route placeholders cannot use capturing groups. For example `{lang:(en|de)}`
|
||||
is not a valid placeholder, because `()` is a capturing group. Instead you can use either
|
||||
`{lang:en|de}` or `{lang:(?:en|de)}`.
|
||||
|
||||
Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]`
|
||||
will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position,
|
||||
not in the middle of a route.
|
||||
|
||||
```php
|
||||
// This route
|
||||
$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler');
|
||||
// Is equivalent to these two routes
|
||||
$r->addRoute('GET', '/user/{id:\d+}', 'handler');
|
||||
$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler');
|
||||
|
||||
// Multiple nested optional parts are possible as well
|
||||
$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler');
|
||||
|
||||
// This route is NOT valid, because optional parts can only occur at the end
|
||||
$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler');
|
||||
```
|
||||
|
||||
The `$handler` parameter does not necessarily have to be a callback, it could also be a controller
|
||||
class name or any other kind of data you wish to associate with the route. FastRoute only tells you
|
||||
which handler corresponds to your URI, how you interpret it is up to you.
|
||||
|
||||
#### Shorcut methods for common request methods
|
||||
|
||||
For the `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` request methods shortcut methods are available. For example:
|
||||
|
||||
```php
|
||||
$r->get('/get-route', 'get_handler');
|
||||
$r->post('/post-route', 'post_handler');
|
||||
```
|
||||
|
||||
Is equivalent to:
|
||||
|
||||
```php
|
||||
$r->addRoute('GET', '/get-route', 'get_handler');
|
||||
$r->addRoute('POST', '/post-route', 'post_handler');
|
||||
```
|
||||
|
||||
#### Route Groups
|
||||
|
||||
Additionally, you can specify routes inside of a group. All routes defined inside a group will have a common prefix.
|
||||
|
||||
For example, defining your routes as:
|
||||
|
||||
```php
|
||||
$r->addGroup('/admin', function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/do-something', 'handler');
|
||||
$r->addRoute('GET', '/do-another-thing', 'handler');
|
||||
$r->addRoute('GET', '/do-something-else', 'handler');
|
||||
});
|
||||
```
|
||||
|
||||
Will have the same result as:
|
||||
|
||||
```php
|
||||
$r->addRoute('GET', '/admin/do-something', 'handler');
|
||||
$r->addRoute('GET', '/admin/do-another-thing', 'handler');
|
||||
$r->addRoute('GET', '/admin/do-something-else', 'handler');
|
||||
```
|
||||
|
||||
Nested groups are also supported, in which case the prefixes of all the nested groups are combined.
|
||||
|
||||
### Caching
|
||||
|
||||
The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless
|
||||
caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated
|
||||
routing data and construct the dispatcher from the cached information:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$dispatcher = FastRoute\cachedDispatcher(function(FastRoute\RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
|
||||
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
|
||||
$r->addRoute('GET', '/user/{name}', 'handler2');
|
||||
}, [
|
||||
'cacheFile' => __DIR__ . '/route.cache', /* required */
|
||||
'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */
|
||||
]);
|
||||
```
|
||||
|
||||
The second parameter to the function is an options array, which can be used to specify the cache
|
||||
file location, among other things.
|
||||
|
||||
### Dispatching a URI
|
||||
|
||||
A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method
|
||||
accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them
|
||||
appropriately) is your job - this library is not bound to the PHP web SAPIs.
|
||||
|
||||
The `dispatch()` method returns an array whose first element contains a status code. It is one
|
||||
of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the
|
||||
method not allowed status the second array element contains a list of HTTP methods allowed for
|
||||
the supplied URI. For example:
|
||||
|
||||
[FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']]
|
||||
|
||||
> **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the
|
||||
`Allow:` header to detail available methods for the requested resource. Applications using FastRoute
|
||||
should use the second array element to add this header when relaying a 405 response.
|
||||
|
||||
For the found status the second array element is the handler that was associated with the route
|
||||
and the third array element is a dictionary of placeholder names to their values. For example:
|
||||
|
||||
/* Routing against GET /user/nikic/42 */
|
||||
|
||||
[FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']]
|
||||
|
||||
### Overriding the route parser and dispatcher
|
||||
|
||||
The routing process makes use of three components: A route parser, a data generator and a
|
||||
dispatcher. The three components adhere to the following interfaces:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
interface RouteParser {
|
||||
public function parse($route);
|
||||
}
|
||||
|
||||
interface DataGenerator {
|
||||
public function addRoute($httpMethod, $routeData, $handler);
|
||||
public function getData();
|
||||
}
|
||||
|
||||
interface Dispatcher {
|
||||
const NOT_FOUND = 0, FOUND = 1, METHOD_NOT_ALLOWED = 2;
|
||||
|
||||
public function dispatch($httpMethod, $uri);
|
||||
}
|
||||
```
|
||||
|
||||
The route parser takes a route pattern string and converts it into an array of route infos, where
|
||||
each route info is again an array of it's parts. The structure is best understood using an example:
|
||||
|
||||
/* The route /user/{id:\d+}[/{name}] converts to the following array: */
|
||||
[
|
||||
[
|
||||
'/user/',
|
||||
['id', '\d+'],
|
||||
],
|
||||
[
|
||||
'/user/',
|
||||
['id', '\d+'],
|
||||
'/',
|
||||
['name', '[^/]+'],
|
||||
],
|
||||
]
|
||||
|
||||
This array can then be passed to the `addRoute()` method of a data generator. After all routes have
|
||||
been added the `getData()` of the generator is invoked, which returns all the routing data required
|
||||
by the dispatcher. The format of this data is not further specified - it is tightly coupled to
|
||||
the corresponding dispatcher.
|
||||
|
||||
The dispatcher accepts the routing data via a constructor and provides a `dispatch()` method, which
|
||||
you're already familiar with.
|
||||
|
||||
The route parser can be overwritten individually (to make use of some different pattern syntax),
|
||||
however the data generator and dispatcher should always be changed as a pair, as the output from
|
||||
the former is tightly coupled to the input of the latter. The reason the generator and the
|
||||
dispatcher are separate is that only the latter is needed when using caching (as the output of
|
||||
the former is what is being cached.)
|
||||
|
||||
When using the `simpleDispatcher` / `cachedDispatcher` functions from above the override happens
|
||||
through the options array:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
|
||||
/* ... */
|
||||
}, [
|
||||
'routeParser' => 'FastRoute\\RouteParser\\Std',
|
||||
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
|
||||
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
|
||||
]);
|
||||
```
|
||||
|
||||
The above options array corresponds to the defaults. By replacing `GroupCountBased` by
|
||||
`GroupPosBased` you could switch to a different dispatching strategy.
|
||||
|
||||
### A Note on HEAD Requests
|
||||
|
||||
The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]:
|
||||
|
||||
> The methods GET and HEAD MUST be supported by all general-purpose servers
|
||||
|
||||
To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an
|
||||
available GET route for a given resource. The PHP web SAPI transparently removes the entity body
|
||||
from HEAD responses so this behavior has no effect on the vast majority of users.
|
||||
|
||||
However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST
|
||||
NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is
|
||||
*your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases.
|
||||
|
||||
Finally, note that applications MAY always specify their own HEAD method route for a given
|
||||
resource to bypass this behavior entirely.
|
||||
|
||||
### Credits
|
||||
|
||||
This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server.
|
||||
|
||||
A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey].
|
||||
|
||||
|
||||
[2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1"
|
||||
[blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html
|
||||
[levi]: https://github.com/morrisonlevi
|
||||
[rdlowrey]: https://github.com/rdlowrey
|
||||
24
vendor/nikic/fast-route/composer.json
vendored
24
vendor/nikic/fast-route/composer.json
vendored
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "nikic/fast-route",
|
||||
"description": "Fast request router for PHP",
|
||||
"keywords": ["routing", "router"],
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nikita Popov",
|
||||
"email": "nikic@php.net"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FastRoute\\": "src/"
|
||||
},
|
||||
"files": ["src/functions.php"]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35|~5.7"
|
||||
}
|
||||
}
|
||||
24
vendor/nikic/fast-route/phpunit.xml
vendored
24
vendor/nikic/fast-route/phpunit.xml
vendored
@@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="test/bootstrap.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="FastRoute Tests">
|
||||
<directory>./test/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
28
vendor/nikic/fast-route/psalm.xml
vendored
28
vendor/nikic/fast-route/psalm.xml
vendored
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
name="Example Psalm config with recommended defaults"
|
||||
stopOnFirstError="false"
|
||||
useDocblockTypes="true"
|
||||
totallyTyped="false"
|
||||
requireVoidReturnType="false"
|
||||
>
|
||||
<projectFiles>
|
||||
<directory name="src" />
|
||||
</projectFiles>
|
||||
|
||||
<issueHandlers>
|
||||
<LessSpecificReturnType errorLevel="info" />
|
||||
|
||||
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
|
||||
<DeprecatedMethod errorLevel="info" />
|
||||
|
||||
<MissingClosureReturnType errorLevel="info" />
|
||||
<MissingReturnType errorLevel="info" />
|
||||
<MissingPropertyType errorLevel="info" />
|
||||
<InvalidDocblock errorLevel="info" />
|
||||
<MisplacedRequiredParam errorLevel="info" />
|
||||
|
||||
<PropertyNotSetInConstructor errorLevel="info" />
|
||||
<MissingConstructor errorLevel="info" />
|
||||
</issueHandlers>
|
||||
</psalm>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
class BadRouteException extends \LogicException
|
||||
{
|
||||
}
|
||||
26
vendor/nikic/fast-route/src/DataGenerator.php
vendored
26
vendor/nikic/fast-route/src/DataGenerator.php
vendored
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
interface DataGenerator
|
||||
{
|
||||
/**
|
||||
* Adds a route to the data generator. The route data uses the
|
||||
* same format that is returned by RouterParser::parser().
|
||||
*
|
||||
* The handler doesn't necessarily need to be a callable, it
|
||||
* can be arbitrary data that will be returned when the route
|
||||
* matches.
|
||||
*
|
||||
* @param string $httpMethod
|
||||
* @param array $routeData
|
||||
* @param mixed $handler
|
||||
*/
|
||||
public function addRoute($httpMethod, $routeData, $handler);
|
||||
|
||||
/**
|
||||
* Returns dispatcher data in some unspecified format, which
|
||||
* depends on the used method of dispatch.
|
||||
*/
|
||||
public function getData();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\DataGenerator;
|
||||
|
||||
class CharCountBased extends RegexBasedAbstract
|
||||
{
|
||||
protected function getApproxChunkSize()
|
||||
{
|
||||
return 30;
|
||||
}
|
||||
|
||||
protected function processChunk($regexToRoutesMap)
|
||||
{
|
||||
$routeMap = [];
|
||||
$regexes = [];
|
||||
|
||||
$suffixLen = 0;
|
||||
$suffix = '';
|
||||
$count = count($regexToRoutesMap);
|
||||
foreach ($regexToRoutesMap as $regex => $route) {
|
||||
$suffixLen++;
|
||||
$suffix .= "\t";
|
||||
|
||||
$regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})';
|
||||
$routeMap[$suffix] = [$route->handler, $route->variables];
|
||||
}
|
||||
|
||||
$regex = '~^(?|' . implode('|', $regexes) . ')$~';
|
||||
return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap];
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\DataGenerator;
|
||||
|
||||
class GroupCountBased extends RegexBasedAbstract
|
||||
{
|
||||
protected function getApproxChunkSize()
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
protected function processChunk($regexToRoutesMap)
|
||||
{
|
||||
$routeMap = [];
|
||||
$regexes = [];
|
||||
$numGroups = 0;
|
||||
foreach ($regexToRoutesMap as $regex => $route) {
|
||||
$numVariables = count($route->variables);
|
||||
$numGroups = max($numGroups, $numVariables);
|
||||
|
||||
$regexes[] = $regex . str_repeat('()', $numGroups - $numVariables);
|
||||
$routeMap[$numGroups + 1] = [$route->handler, $route->variables];
|
||||
|
||||
++$numGroups;
|
||||
}
|
||||
|
||||
$regex = '~^(?|' . implode('|', $regexes) . ')$~';
|
||||
return ['regex' => $regex, 'routeMap' => $routeMap];
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\DataGenerator;
|
||||
|
||||
class GroupPosBased extends RegexBasedAbstract
|
||||
{
|
||||
protected function getApproxChunkSize()
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
protected function processChunk($regexToRoutesMap)
|
||||
{
|
||||
$routeMap = [];
|
||||
$regexes = [];
|
||||
$offset = 1;
|
||||
foreach ($regexToRoutesMap as $regex => $route) {
|
||||
$regexes[] = $regex;
|
||||
$routeMap[$offset] = [$route->handler, $route->variables];
|
||||
|
||||
$offset += count($route->variables);
|
||||
}
|
||||
|
||||
$regex = '~^(?:' . implode('|', $regexes) . ')$~';
|
||||
return ['regex' => $regex, 'routeMap' => $routeMap];
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\DataGenerator;
|
||||
|
||||
class MarkBased extends RegexBasedAbstract
|
||||
{
|
||||
protected function getApproxChunkSize()
|
||||
{
|
||||
return 30;
|
||||
}
|
||||
|
||||
protected function processChunk($regexToRoutesMap)
|
||||
{
|
||||
$routeMap = [];
|
||||
$regexes = [];
|
||||
$markName = 'a';
|
||||
foreach ($regexToRoutesMap as $regex => $route) {
|
||||
$regexes[] = $regex . '(*MARK:' . $markName . ')';
|
||||
$routeMap[$markName] = [$route->handler, $route->variables];
|
||||
|
||||
++$markName;
|
||||
}
|
||||
|
||||
$regex = '~^(?|' . implode('|', $regexes) . ')$~';
|
||||
return ['regex' => $regex, 'routeMap' => $routeMap];
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\DataGenerator;
|
||||
|
||||
use FastRoute\BadRouteException;
|
||||
use FastRoute\DataGenerator;
|
||||
use FastRoute\Route;
|
||||
|
||||
abstract class RegexBasedAbstract implements DataGenerator
|
||||
{
|
||||
/** @var mixed[][] */
|
||||
protected $staticRoutes = [];
|
||||
|
||||
/** @var Route[][] */
|
||||
protected $methodToRegexToRoutesMap = [];
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
abstract protected function getApproxChunkSize();
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
abstract protected function processChunk($regexToRoutesMap);
|
||||
|
||||
public function addRoute($httpMethod, $routeData, $handler)
|
||||
{
|
||||
if ($this->isStaticRoute($routeData)) {
|
||||
$this->addStaticRoute($httpMethod, $routeData, $handler);
|
||||
} else {
|
||||
$this->addVariableRoute($httpMethod, $routeData, $handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
if (empty($this->methodToRegexToRoutesMap)) {
|
||||
return [$this->staticRoutes, []];
|
||||
}
|
||||
|
||||
return [$this->staticRoutes, $this->generateVariableRouteData()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function generateVariableRouteData()
|
||||
{
|
||||
$data = [];
|
||||
foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) {
|
||||
$chunkSize = $this->computeChunkSize(count($regexToRoutesMap));
|
||||
$chunks = array_chunk($regexToRoutesMap, $chunkSize, true);
|
||||
$data[$method] = array_map([$this, 'processChunk'], $chunks);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int
|
||||
* @return int
|
||||
*/
|
||||
private function computeChunkSize($count)
|
||||
{
|
||||
$numParts = max(1, round($count / $this->getApproxChunkSize()));
|
||||
return (int) ceil($count / $numParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[]
|
||||
* @return bool
|
||||
*/
|
||||
private function isStaticRoute($routeData)
|
||||
{
|
||||
return count($routeData) === 1 && is_string($routeData[0]);
|
||||
}
|
||||
|
||||
private function addStaticRoute($httpMethod, $routeData, $handler)
|
||||
{
|
||||
$routeStr = $routeData[0];
|
||||
|
||||
if (isset($this->staticRoutes[$httpMethod][$routeStr])) {
|
||||
throw new BadRouteException(sprintf(
|
||||
'Cannot register two routes matching "%s" for method "%s"',
|
||||
$routeStr, $httpMethod
|
||||
));
|
||||
}
|
||||
|
||||
if (isset($this->methodToRegexToRoutesMap[$httpMethod])) {
|
||||
foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) {
|
||||
if ($route->matches($routeStr)) {
|
||||
throw new BadRouteException(sprintf(
|
||||
'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"',
|
||||
$routeStr, $route->regex, $httpMethod
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->staticRoutes[$httpMethod][$routeStr] = $handler;
|
||||
}
|
||||
|
||||
private function addVariableRoute($httpMethod, $routeData, $handler)
|
||||
{
|
||||
list($regex, $variables) = $this->buildRegexForRoute($routeData);
|
||||
|
||||
if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) {
|
||||
throw new BadRouteException(sprintf(
|
||||
'Cannot register two routes matching "%s" for method "%s"',
|
||||
$regex, $httpMethod
|
||||
));
|
||||
}
|
||||
|
||||
$this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route(
|
||||
$httpMethod, $handler, $regex, $variables
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[]
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function buildRegexForRoute($routeData)
|
||||
{
|
||||
$regex = '';
|
||||
$variables = [];
|
||||
foreach ($routeData as $part) {
|
||||
if (is_string($part)) {
|
||||
$regex .= preg_quote($part, '~');
|
||||
continue;
|
||||
}
|
||||
|
||||
list($varName, $regexPart) = $part;
|
||||
|
||||
if (isset($variables[$varName])) {
|
||||
throw new BadRouteException(sprintf(
|
||||
'Cannot use the same placeholder "%s" twice', $varName
|
||||
));
|
||||
}
|
||||
|
||||
if ($this->regexHasCapturingGroups($regexPart)) {
|
||||
throw new BadRouteException(sprintf(
|
||||
'Regex "%s" for parameter "%s" contains a capturing group',
|
||||
$regexPart, $varName
|
||||
));
|
||||
}
|
||||
|
||||
$variables[$varName] = $varName;
|
||||
$regex .= '(' . $regexPart . ')';
|
||||
}
|
||||
|
||||
return [$regex, $variables];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
private function regexHasCapturingGroups($regex)
|
||||
{
|
||||
if (false === strpos($regex, '(')) {
|
||||
// Needs to have at least a ( to contain a capturing group
|
||||
return false;
|
||||
}
|
||||
|
||||
// Semi-accurate detection for capturing groups
|
||||
return (bool) preg_match(
|
||||
'~
|
||||
(?:
|
||||
\(\?\(
|
||||
| \[ [^\]\\\\]* (?: \\\\ . [^\]\\\\]* )* \]
|
||||
| \\\\ .
|
||||
) (*SKIP)(*FAIL) |
|
||||
\(
|
||||
(?!
|
||||
\? (?! <(?![!=]) | P< | \' )
|
||||
| \*
|
||||
)
|
||||
~x',
|
||||
$regex
|
||||
);
|
||||
}
|
||||
}
|
||||
26
vendor/nikic/fast-route/src/Dispatcher.php
vendored
26
vendor/nikic/fast-route/src/Dispatcher.php
vendored
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
interface Dispatcher
|
||||
{
|
||||
const NOT_FOUND = 0;
|
||||
const FOUND = 1;
|
||||
const METHOD_NOT_ALLOWED = 2;
|
||||
|
||||
/**
|
||||
* Dispatches against the provided HTTP method verb and URI.
|
||||
*
|
||||
* Returns array with one of the following formats:
|
||||
*
|
||||
* [self::NOT_FOUND]
|
||||
* [self::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
|
||||
* [self::FOUND, $handler, ['varName' => 'value', ...]]
|
||||
*
|
||||
* @param string $httpMethod
|
||||
* @param string $uri
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function dispatch($httpMethod, $uri);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
class CharCountBased extends RegexBasedAbstract
|
||||
{
|
||||
public function __construct($data)
|
||||
{
|
||||
list($this->staticRouteMap, $this->variableRouteData) = $data;
|
||||
}
|
||||
|
||||
protected function dispatchVariableRoute($routeData, $uri)
|
||||
{
|
||||
foreach ($routeData as $data) {
|
||||
if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($handler, $varNames) = $data['routeMap'][end($matches)];
|
||||
|
||||
$vars = [];
|
||||
$i = 0;
|
||||
foreach ($varNames as $varName) {
|
||||
$vars[$varName] = $matches[++$i];
|
||||
}
|
||||
return [self::FOUND, $handler, $vars];
|
||||
}
|
||||
|
||||
return [self::NOT_FOUND];
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
class GroupCountBased extends RegexBasedAbstract
|
||||
{
|
||||
public function __construct($data)
|
||||
{
|
||||
list($this->staticRouteMap, $this->variableRouteData) = $data;
|
||||
}
|
||||
|
||||
protected function dispatchVariableRoute($routeData, $uri)
|
||||
{
|
||||
foreach ($routeData as $data) {
|
||||
if (!preg_match($data['regex'], $uri, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($handler, $varNames) = $data['routeMap'][count($matches)];
|
||||
|
||||
$vars = [];
|
||||
$i = 0;
|
||||
foreach ($varNames as $varName) {
|
||||
$vars[$varName] = $matches[++$i];
|
||||
}
|
||||
return [self::FOUND, $handler, $vars];
|
||||
}
|
||||
|
||||
return [self::NOT_FOUND];
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
class GroupPosBased extends RegexBasedAbstract
|
||||
{
|
||||
public function __construct($data)
|
||||
{
|
||||
list($this->staticRouteMap, $this->variableRouteData) = $data;
|
||||
}
|
||||
|
||||
protected function dispatchVariableRoute($routeData, $uri)
|
||||
{
|
||||
foreach ($routeData as $data) {
|
||||
if (!preg_match($data['regex'], $uri, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// find first non-empty match
|
||||
for ($i = 1; '' === $matches[$i]; ++$i);
|
||||
|
||||
list($handler, $varNames) = $data['routeMap'][$i];
|
||||
|
||||
$vars = [];
|
||||
foreach ($varNames as $varName) {
|
||||
$vars[$varName] = $matches[$i++];
|
||||
}
|
||||
return [self::FOUND, $handler, $vars];
|
||||
}
|
||||
|
||||
return [self::NOT_FOUND];
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
class MarkBased extends RegexBasedAbstract
|
||||
{
|
||||
public function __construct($data)
|
||||
{
|
||||
list($this->staticRouteMap, $this->variableRouteData) = $data;
|
||||
}
|
||||
|
||||
protected function dispatchVariableRoute($routeData, $uri)
|
||||
{
|
||||
foreach ($routeData as $data) {
|
||||
if (!preg_match($data['regex'], $uri, $matches)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($handler, $varNames) = $data['routeMap'][$matches['MARK']];
|
||||
|
||||
$vars = [];
|
||||
$i = 0;
|
||||
foreach ($varNames as $varName) {
|
||||
$vars[$varName] = $matches[++$i];
|
||||
}
|
||||
return [self::FOUND, $handler, $vars];
|
||||
}
|
||||
|
||||
return [self::NOT_FOUND];
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
use FastRoute\Dispatcher;
|
||||
|
||||
abstract class RegexBasedAbstract implements Dispatcher
|
||||
{
|
||||
/** @var mixed[][] */
|
||||
protected $staticRouteMap = [];
|
||||
|
||||
/** @var mixed[] */
|
||||
protected $variableRouteData = [];
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
abstract protected function dispatchVariableRoute($routeData, $uri);
|
||||
|
||||
public function dispatch($httpMethod, $uri)
|
||||
{
|
||||
if (isset($this->staticRouteMap[$httpMethod][$uri])) {
|
||||
$handler = $this->staticRouteMap[$httpMethod][$uri];
|
||||
return [self::FOUND, $handler, []];
|
||||
}
|
||||
|
||||
$varRouteData = $this->variableRouteData;
|
||||
if (isset($varRouteData[$httpMethod])) {
|
||||
$result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri);
|
||||
if ($result[0] === self::FOUND) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// For HEAD requests, attempt fallback to GET
|
||||
if ($httpMethod === 'HEAD') {
|
||||
if (isset($this->staticRouteMap['GET'][$uri])) {
|
||||
$handler = $this->staticRouteMap['GET'][$uri];
|
||||
return [self::FOUND, $handler, []];
|
||||
}
|
||||
if (isset($varRouteData['GET'])) {
|
||||
$result = $this->dispatchVariableRoute($varRouteData['GET'], $uri);
|
||||
if ($result[0] === self::FOUND) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing else matches, try fallback routes
|
||||
if (isset($this->staticRouteMap['*'][$uri])) {
|
||||
$handler = $this->staticRouteMap['*'][$uri];
|
||||
return [self::FOUND, $handler, []];
|
||||
}
|
||||
if (isset($varRouteData['*'])) {
|
||||
$result = $this->dispatchVariableRoute($varRouteData['*'], $uri);
|
||||
if ($result[0] === self::FOUND) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Find allowed methods for this URI by matching against all other HTTP methods as well
|
||||
$allowedMethods = [];
|
||||
|
||||
foreach ($this->staticRouteMap as $method => $uriMap) {
|
||||
if ($method !== $httpMethod && isset($uriMap[$uri])) {
|
||||
$allowedMethods[] = $method;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($varRouteData as $method => $routeData) {
|
||||
if ($method === $httpMethod) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $this->dispatchVariableRoute($routeData, $uri);
|
||||
if ($result[0] === self::FOUND) {
|
||||
$allowedMethods[] = $method;
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no allowed methods the route simply does not exist
|
||||
if ($allowedMethods) {
|
||||
return [self::METHOD_NOT_ALLOWED, $allowedMethods];
|
||||
}
|
||||
|
||||
return [self::NOT_FOUND];
|
||||
}
|
||||
}
|
||||
47
vendor/nikic/fast-route/src/Route.php
vendored
47
vendor/nikic/fast-route/src/Route.php
vendored
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
class Route
|
||||
{
|
||||
/** @var string */
|
||||
public $httpMethod;
|
||||
|
||||
/** @var string */
|
||||
public $regex;
|
||||
|
||||
/** @var array */
|
||||
public $variables;
|
||||
|
||||
/** @var mixed */
|
||||
public $handler;
|
||||
|
||||
/**
|
||||
* Constructs a route (value object).
|
||||
*
|
||||
* @param string $httpMethod
|
||||
* @param mixed $handler
|
||||
* @param string $regex
|
||||
* @param array $variables
|
||||
*/
|
||||
public function __construct($httpMethod, $handler, $regex, $variables)
|
||||
{
|
||||
$this->httpMethod = $httpMethod;
|
||||
$this->handler = $handler;
|
||||
$this->regex = $regex;
|
||||
$this->variables = $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether this route matches the given string.
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matches($str)
|
||||
{
|
||||
$regex = '~^' . $this->regex . '$~';
|
||||
return (bool) preg_match($regex, $str);
|
||||
}
|
||||
}
|
||||
152
vendor/nikic/fast-route/src/RouteCollector.php
vendored
152
vendor/nikic/fast-route/src/RouteCollector.php
vendored
@@ -1,152 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
class RouteCollector
|
||||
{
|
||||
/** @var RouteParser */
|
||||
protected $routeParser;
|
||||
|
||||
/** @var DataGenerator */
|
||||
protected $dataGenerator;
|
||||
|
||||
/** @var string */
|
||||
protected $currentGroupPrefix;
|
||||
|
||||
/**
|
||||
* Constructs a route collector.
|
||||
*
|
||||
* @param RouteParser $routeParser
|
||||
* @param DataGenerator $dataGenerator
|
||||
*/
|
||||
public function __construct(RouteParser $routeParser, DataGenerator $dataGenerator)
|
||||
{
|
||||
$this->routeParser = $routeParser;
|
||||
$this->dataGenerator = $dataGenerator;
|
||||
$this->currentGroupPrefix = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a route to the collection.
|
||||
*
|
||||
* The syntax used in the $route string depends on the used route parser.
|
||||
*
|
||||
* @param string|string[] $httpMethod
|
||||
* @param string $route
|
||||
* @param mixed $handler
|
||||
*/
|
||||
public function addRoute($httpMethod, $route, $handler)
|
||||
{
|
||||
$route = $this->currentGroupPrefix . $route;
|
||||
$routeDatas = $this->routeParser->parse($route);
|
||||
foreach ((array) $httpMethod as $method) {
|
||||
foreach ($routeDatas as $routeData) {
|
||||
$this->dataGenerator->addRoute($method, $routeData, $handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a route group with a common prefix.
|
||||
*
|
||||
* All routes created in the passed callback will have the given group prefix prepended.
|
||||
*
|
||||
* @param string $prefix
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function addGroup($prefix, callable $callback)
|
||||
{
|
||||
$previousGroupPrefix = $this->currentGroupPrefix;
|
||||
$this->currentGroupPrefix = $previousGroupPrefix . $prefix;
|
||||
$callback($this);
|
||||
$this->currentGroupPrefix = $previousGroupPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a GET route to the collection
|
||||
*
|
||||
* This is simply an alias of $this->addRoute('GET', $route, $handler)
|
||||
*
|
||||
* @param string $route
|
||||
* @param mixed $handler
|
||||
*/
|
||||
public function get($route, $handler)
|
||||
{
|
||||
$this->addRoute('GET', $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a POST route to the collection
|
||||
*
|
||||
* This is simply an alias of $this->addRoute('POST', $route, $handler)
|
||||
*
|
||||
* @param string $route
|
||||
* @param mixed $handler
|
||||
*/
|
||||
public function post($route, $handler)
|
||||
{
|
||||
$this->addRoute('POST', $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a PUT route to the collection
|
||||
*
|
||||
* This is simply an alias of $this->addRoute('PUT', $route, $handler)
|
||||
*
|
||||
* @param string $route
|
||||
* @param mixed $handler
|
||||
*/
|
||||
public function put($route, $handler)
|
||||
{
|
||||
$this->addRoute('PUT', $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a DELETE route to the collection
|
||||
*
|
||||
* This is simply an alias of $this->addRoute('DELETE', $route, $handler)
|
||||
*
|
||||
* @param string $route
|
||||
* @param mixed $handler
|
||||
*/
|
||||
public function delete($route, $handler)
|
||||
{
|
||||
$this->addRoute('DELETE', $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a PATCH route to the collection
|
||||
*
|
||||
* This is simply an alias of $this->addRoute('PATCH', $route, $handler)
|
||||
*
|
||||
* @param string $route
|
||||
* @param mixed $handler
|
||||
*/
|
||||
public function patch($route, $handler)
|
||||
{
|
||||
$this->addRoute('PATCH', $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a HEAD route to the collection
|
||||
*
|
||||
* This is simply an alias of $this->addRoute('HEAD', $route, $handler)
|
||||
*
|
||||
* @param string $route
|
||||
* @param mixed $handler
|
||||
*/
|
||||
public function head($route, $handler)
|
||||
{
|
||||
$this->addRoute('HEAD', $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the collected route data, as provided by the data generator.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->dataGenerator->getData();
|
||||
}
|
||||
}
|
||||
37
vendor/nikic/fast-route/src/RouteParser.php
vendored
37
vendor/nikic/fast-route/src/RouteParser.php
vendored
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
interface RouteParser
|
||||
{
|
||||
/**
|
||||
* Parses a route string into multiple route data arrays.
|
||||
*
|
||||
* The expected output is defined using an example:
|
||||
*
|
||||
* For the route string "/fixedRoutePart/{varName}[/moreFixed/{varName2:\d+}]", if {varName} is interpreted as
|
||||
* a placeholder and [...] is interpreted as an optional route part, the expected result is:
|
||||
*
|
||||
* [
|
||||
* // first route: without optional part
|
||||
* [
|
||||
* "/fixedRoutePart/",
|
||||
* ["varName", "[^/]+"],
|
||||
* ],
|
||||
* // second route: with optional part
|
||||
* [
|
||||
* "/fixedRoutePart/",
|
||||
* ["varName", "[^/]+"],
|
||||
* "/moreFixed/",
|
||||
* ["varName2", [0-9]+"],
|
||||
* ],
|
||||
* ]
|
||||
*
|
||||
* Here one route string was converted into two route data arrays.
|
||||
*
|
||||
* @param string $route Route string to parse
|
||||
*
|
||||
* @return mixed[][] Array of route data arrays
|
||||
*/
|
||||
public function parse($route);
|
||||
}
|
||||
87
vendor/nikic/fast-route/src/RouteParser/Std.php
vendored
87
vendor/nikic/fast-route/src/RouteParser/Std.php
vendored
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\RouteParser;
|
||||
|
||||
use FastRoute\BadRouteException;
|
||||
use FastRoute\RouteParser;
|
||||
|
||||
/**
|
||||
* Parses route strings of the following form:
|
||||
*
|
||||
* "/user/{name}[/{id:[0-9]+}]"
|
||||
*/
|
||||
class Std implements RouteParser
|
||||
{
|
||||
const VARIABLE_REGEX = <<<'REGEX'
|
||||
\{
|
||||
\s* ([a-zA-Z_][a-zA-Z0-9_-]*) \s*
|
||||
(?:
|
||||
: \s* ([^{}]*(?:\{(?-1)\}[^{}]*)*)
|
||||
)?
|
||||
\}
|
||||
REGEX;
|
||||
const DEFAULT_DISPATCH_REGEX = '[^/]+';
|
||||
|
||||
public function parse($route)
|
||||
{
|
||||
$routeWithoutClosingOptionals = rtrim($route, ']');
|
||||
$numOptionals = strlen($route) - strlen($routeWithoutClosingOptionals);
|
||||
|
||||
// Split on [ while skipping placeholders
|
||||
$segments = preg_split('~' . self::VARIABLE_REGEX . '(*SKIP)(*F) | \[~x', $routeWithoutClosingOptionals);
|
||||
if ($numOptionals !== count($segments) - 1) {
|
||||
// If there are any ] in the middle of the route, throw a more specific error message
|
||||
if (preg_match('~' . self::VARIABLE_REGEX . '(*SKIP)(*F) | \]~x', $routeWithoutClosingOptionals)) {
|
||||
throw new BadRouteException('Optional segments can only occur at the end of a route');
|
||||
}
|
||||
throw new BadRouteException("Number of opening '[' and closing ']' does not match");
|
||||
}
|
||||
|
||||
$currentRoute = '';
|
||||
$routeDatas = [];
|
||||
foreach ($segments as $n => $segment) {
|
||||
if ($segment === '' && $n !== 0) {
|
||||
throw new BadRouteException('Empty optional part');
|
||||
}
|
||||
|
||||
$currentRoute .= $segment;
|
||||
$routeDatas[] = $this->parsePlaceholders($currentRoute);
|
||||
}
|
||||
return $routeDatas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a route string that does not contain optional segments.
|
||||
*
|
||||
* @param string
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function parsePlaceholders($route)
|
||||
{
|
||||
if (!preg_match_all(
|
||||
'~' . self::VARIABLE_REGEX . '~x', $route, $matches,
|
||||
PREG_OFFSET_CAPTURE | PREG_SET_ORDER
|
||||
)) {
|
||||
return [$route];
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
$routeData = [];
|
||||
foreach ($matches as $set) {
|
||||
if ($set[0][1] > $offset) {
|
||||
$routeData[] = substr($route, $offset, $set[0][1] - $offset);
|
||||
}
|
||||
$routeData[] = [
|
||||
$set[1][0],
|
||||
isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX
|
||||
];
|
||||
$offset = $set[0][1] + strlen($set[0][0]);
|
||||
}
|
||||
|
||||
if ($offset !== strlen($route)) {
|
||||
$routeData[] = substr($route, $offset);
|
||||
}
|
||||
|
||||
return $routeData;
|
||||
}
|
||||
}
|
||||
12
vendor/nikic/fast-route/src/bootstrap.php
vendored
12
vendor/nikic/fast-route/src/bootstrap.php
vendored
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
require __DIR__ . '/functions.php';
|
||||
|
||||
spl_autoload_register(function ($class) {
|
||||
if (strpos($class, 'FastRoute\\') === 0) {
|
||||
$name = substr($class, strlen('FastRoute'));
|
||||
require __DIR__ . strtr($name, '\\', DIRECTORY_SEPARATOR) . '.php';
|
||||
}
|
||||
});
|
||||
74
vendor/nikic/fast-route/src/functions.php
vendored
74
vendor/nikic/fast-route/src/functions.php
vendored
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
if (!function_exists('FastRoute\simpleDispatcher')) {
|
||||
/**
|
||||
* @param callable $routeDefinitionCallback
|
||||
* @param array $options
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
function simpleDispatcher(callable $routeDefinitionCallback, array $options = [])
|
||||
{
|
||||
$options += [
|
||||
'routeParser' => 'FastRoute\\RouteParser\\Std',
|
||||
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
|
||||
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
|
||||
'routeCollector' => 'FastRoute\\RouteCollector',
|
||||
];
|
||||
|
||||
/** @var RouteCollector $routeCollector */
|
||||
$routeCollector = new $options['routeCollector'](
|
||||
new $options['routeParser'], new $options['dataGenerator']
|
||||
);
|
||||
$routeDefinitionCallback($routeCollector);
|
||||
|
||||
return new $options['dispatcher']($routeCollector->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $routeDefinitionCallback
|
||||
* @param array $options
|
||||
*
|
||||
* @return Dispatcher
|
||||
*/
|
||||
function cachedDispatcher(callable $routeDefinitionCallback, array $options = [])
|
||||
{
|
||||
$options += [
|
||||
'routeParser' => 'FastRoute\\RouteParser\\Std',
|
||||
'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased',
|
||||
'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased',
|
||||
'routeCollector' => 'FastRoute\\RouteCollector',
|
||||
'cacheDisabled' => false,
|
||||
];
|
||||
|
||||
if (!isset($options['cacheFile'])) {
|
||||
throw new \LogicException('Must specify "cacheFile" option');
|
||||
}
|
||||
|
||||
if (!$options['cacheDisabled'] && file_exists($options['cacheFile'])) {
|
||||
$dispatchData = require $options['cacheFile'];
|
||||
if (!is_array($dispatchData)) {
|
||||
throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"');
|
||||
}
|
||||
return new $options['dispatcher']($dispatchData);
|
||||
}
|
||||
|
||||
$routeCollector = new $options['routeCollector'](
|
||||
new $options['routeParser'], new $options['dataGenerator']
|
||||
);
|
||||
$routeDefinitionCallback($routeCollector);
|
||||
|
||||
/** @var RouteCollector $routeCollector */
|
||||
$dispatchData = $routeCollector->getData();
|
||||
if (!$options['cacheDisabled']) {
|
||||
file_put_contents(
|
||||
$options['cacheFile'],
|
||||
'<?php return ' . var_export($dispatchData, true) . ';'
|
||||
);
|
||||
}
|
||||
|
||||
return new $options['dispatcher']($dispatchData);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
class CharCountBasedTest extends DispatcherTest
|
||||
{
|
||||
protected function getDispatcherClass()
|
||||
{
|
||||
return 'FastRoute\\Dispatcher\\CharCountBased';
|
||||
}
|
||||
|
||||
protected function getDataGeneratorClass()
|
||||
{
|
||||
return 'FastRoute\\DataGenerator\\CharCountBased';
|
||||
}
|
||||
}
|
||||
@@ -1,581 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
use FastRoute\RouteCollector;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
abstract class DispatcherTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Delegate dispatcher selection to child test classes
|
||||
*/
|
||||
abstract protected function getDispatcherClass();
|
||||
|
||||
/**
|
||||
* Delegate dataGenerator selection to child test classes
|
||||
*/
|
||||
abstract protected function getDataGeneratorClass();
|
||||
|
||||
/**
|
||||
* Set appropriate options for the specific Dispatcher class we're testing
|
||||
*/
|
||||
private function generateDispatcherOptions()
|
||||
{
|
||||
return [
|
||||
'dataGenerator' => $this->getDataGeneratorClass(),
|
||||
'dispatcher' => $this->getDispatcherClass()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideFoundDispatchCases
|
||||
*/
|
||||
public function testFoundDispatches($method, $uri, $callback, $handler, $argDict)
|
||||
{
|
||||
$dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
|
||||
$info = $dispatcher->dispatch($method, $uri);
|
||||
$this->assertSame($dispatcher::FOUND, $info[0]);
|
||||
$this->assertSame($handler, $info[1]);
|
||||
$this->assertSame($argDict, $info[2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideNotFoundDispatchCases
|
||||
*/
|
||||
public function testNotFoundDispatches($method, $uri, $callback)
|
||||
{
|
||||
$dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
|
||||
$routeInfo = $dispatcher->dispatch($method, $uri);
|
||||
$this->assertArrayNotHasKey(1, $routeInfo,
|
||||
'NOT_FOUND result must only contain a single element in the returned info array'
|
||||
);
|
||||
$this->assertSame($dispatcher::NOT_FOUND, $routeInfo[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideMethodNotAllowedDispatchCases
|
||||
*/
|
||||
public function testMethodNotAllowedDispatches($method, $uri, $callback, $availableMethods)
|
||||
{
|
||||
$dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions());
|
||||
$routeInfo = $dispatcher->dispatch($method, $uri);
|
||||
$this->assertArrayHasKey(1, $routeInfo,
|
||||
'METHOD_NOT_ALLOWED result must return an array of allowed methods at index 1'
|
||||
);
|
||||
|
||||
list($routedStatus, $methodArray) = $dispatcher->dispatch($method, $uri);
|
||||
$this->assertSame($dispatcher::METHOD_NOT_ALLOWED, $routedStatus);
|
||||
$this->assertSame($availableMethods, $methodArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \FastRoute\BadRouteException
|
||||
* @expectedExceptionMessage Cannot use the same placeholder "test" twice
|
||||
*/
|
||||
public function testDuplicateVariableNameError()
|
||||
{
|
||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/foo/{test}/{test:\d+}', 'handler0');
|
||||
}, $this->generateDispatcherOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \FastRoute\BadRouteException
|
||||
* @expectedExceptionMessage Cannot register two routes matching "/user/([^/]+)" for method "GET"
|
||||
*/
|
||||
public function testDuplicateVariableRoute()
|
||||
{
|
||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{id}', 'handler0'); // oops, forgot \d+ restriction ;)
|
||||
$r->addRoute('GET', '/user/{name}', 'handler1');
|
||||
}, $this->generateDispatcherOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \FastRoute\BadRouteException
|
||||
* @expectedExceptionMessage Cannot register two routes matching "/user" for method "GET"
|
||||
*/
|
||||
public function testDuplicateStaticRoute()
|
||||
{
|
||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user', 'handler0');
|
||||
$r->addRoute('GET', '/user', 'handler1');
|
||||
}, $this->generateDispatcherOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \FastRoute\BadRouteException
|
||||
* @expectedExceptionMessage Static route "/user/nikic" is shadowed by previously defined variable route "/user/([^/]+)" for method "GET"
|
||||
*/
|
||||
public function testShadowedStaticRoute()
|
||||
{
|
||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
||||
$r->addRoute('GET', '/user/nikic', 'handler1');
|
||||
}, $this->generateDispatcherOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \FastRoute\BadRouteException
|
||||
* @expectedExceptionMessage Regex "(en|de)" for parameter "lang" contains a capturing group
|
||||
*/
|
||||
public function testCapturing()
|
||||
{
|
||||
\FastRoute\simpleDispatcher(function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/{lang:(en|de)}', 'handler0');
|
||||
}, $this->generateDispatcherOptions());
|
||||
}
|
||||
|
||||
public function provideFoundDispatchCases()
|
||||
{
|
||||
$cases = [];
|
||||
|
||||
// 0 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/resource/123/456', 'handler0');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/resource/123/456';
|
||||
$handler = 'handler0';
|
||||
$argDict = [];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 1 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/handler0', 'handler0');
|
||||
$r->addRoute('GET', '/handler1', 'handler1');
|
||||
$r->addRoute('GET', '/handler2', 'handler2');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/handler2';
|
||||
$handler = 'handler2';
|
||||
$argDict = [];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 2 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
|
||||
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
|
||||
$r->addRoute('GET', '/user/{name}', 'handler2');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/user/rdlowrey';
|
||||
$handler = 'handler2';
|
||||
$argDict = ['name' => 'rdlowrey'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 3 -------------------------------------------------------------------------------------->
|
||||
|
||||
// reuse $callback from #2
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/user/12345';
|
||||
$handler = 'handler1';
|
||||
$argDict = ['id' => '12345'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 4 -------------------------------------------------------------------------------------->
|
||||
|
||||
// reuse $callback from #3
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/user/NaN';
|
||||
$handler = 'handler2';
|
||||
$argDict = ['name' => 'NaN'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 5 -------------------------------------------------------------------------------------->
|
||||
|
||||
// reuse $callback from #4
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/user/rdlowrey/12345';
|
||||
$handler = 'handler0';
|
||||
$argDict = ['name' => 'rdlowrey', 'id' => '12345'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 6 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler0');
|
||||
$r->addRoute('GET', '/user/12345/extension', 'handler1');
|
||||
$r->addRoute('GET', '/user/{id:[0-9]+}.{extension}', 'handler2');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/user/12345.svg';
|
||||
$handler = 'handler2';
|
||||
$argDict = ['id' => '12345', 'extension' => 'svg'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 7 ----- Test GET method fallback on HEAD route miss ------------------------------------>
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler1');
|
||||
$r->addRoute('GET', '/static0', 'handler2');
|
||||
$r->addRoute('GET', '/static1', 'handler3');
|
||||
$r->addRoute('HEAD', '/static1', 'handler4');
|
||||
};
|
||||
|
||||
$method = 'HEAD';
|
||||
$uri = '/user/rdlowrey';
|
||||
$handler = 'handler0';
|
||||
$argDict = ['name' => 'rdlowrey'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 8 ----- Test GET method fallback on HEAD route miss ------------------------------------>
|
||||
|
||||
// reuse $callback from #7
|
||||
|
||||
$method = 'HEAD';
|
||||
$uri = '/user/rdlowrey/1234';
|
||||
$handler = 'handler1';
|
||||
$argDict = ['name' => 'rdlowrey', 'id' => '1234'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 9 ----- Test GET method fallback on HEAD route miss ------------------------------------>
|
||||
|
||||
// reuse $callback from #8
|
||||
|
||||
$method = 'HEAD';
|
||||
$uri = '/static0';
|
||||
$handler = 'handler2';
|
||||
$argDict = [];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 10 ---- Test existing HEAD route used if available (no fallback) ----------------------->
|
||||
|
||||
// reuse $callback from #9
|
||||
|
||||
$method = 'HEAD';
|
||||
$uri = '/static1';
|
||||
$handler = 'handler4';
|
||||
$argDict = [];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 11 ---- More specified routes are not shadowed by less specific of another method ------>
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
||||
$r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1');
|
||||
};
|
||||
|
||||
$method = 'POST';
|
||||
$uri = '/user/rdlowrey';
|
||||
$handler = 'handler1';
|
||||
$argDict = ['name' => 'rdlowrey'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 12 ---- Handler of more specific routes is used, if it occurs first -------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
||||
$r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1');
|
||||
$r->addRoute('POST', '/user/{name}', 'handler2');
|
||||
};
|
||||
|
||||
$method = 'POST';
|
||||
$uri = '/user/rdlowrey';
|
||||
$handler = 'handler1';
|
||||
$argDict = ['name' => 'rdlowrey'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 13 ---- Route with constant suffix ----------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}', 'handler0');
|
||||
$r->addRoute('GET', '/user/{name}/edit', 'handler1');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/user/rdlowrey/edit';
|
||||
$handler = 'handler1';
|
||||
$argDict = ['name' => 'rdlowrey'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $handler, $argDict];
|
||||
|
||||
// 14 ---- Handle multiple methods with the same handler ---------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost');
|
||||
$r->addRoute(['DELETE'], '/user', 'handlerDelete');
|
||||
$r->addRoute([], '/user', 'handlerNone');
|
||||
};
|
||||
|
||||
$argDict = [];
|
||||
$cases[] = ['GET', '/user', $callback, 'handlerGetPost', $argDict];
|
||||
$cases[] = ['POST', '/user', $callback, 'handlerGetPost', $argDict];
|
||||
$cases[] = ['DELETE', '/user', $callback, 'handlerDelete', $argDict];
|
||||
|
||||
// 17 ----
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('POST', '/user.json', 'handler0');
|
||||
$r->addRoute('GET', '/{entity}.json', 'handler1');
|
||||
};
|
||||
|
||||
$cases[] = ['GET', '/user.json', $callback, 'handler1', ['entity' => 'user']];
|
||||
|
||||
// 18 ----
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '', 'handler0');
|
||||
};
|
||||
|
||||
$cases[] = ['GET', '', $callback, 'handler0', []];
|
||||
|
||||
// 19 ----
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('HEAD', '/a/{foo}', 'handler0');
|
||||
$r->addRoute('GET', '/b/{foo}', 'handler1');
|
||||
};
|
||||
|
||||
$cases[] = ['HEAD', '/b/bar', $callback, 'handler1', ['foo' => 'bar']];
|
||||
|
||||
// 20 ----
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('HEAD', '/a', 'handler0');
|
||||
$r->addRoute('GET', '/b', 'handler1');
|
||||
};
|
||||
|
||||
$cases[] = ['HEAD', '/b', $callback, 'handler1', []];
|
||||
|
||||
// 21 ----
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/foo', 'handler0');
|
||||
$r->addRoute('HEAD', '/{bar}', 'handler1');
|
||||
};
|
||||
|
||||
$cases[] = ['HEAD', '/foo', $callback, 'handler1', ['bar' => 'foo']];
|
||||
|
||||
// 22 ----
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('*', '/user', 'handler0');
|
||||
$r->addRoute('*', '/{user}', 'handler1');
|
||||
$r->addRoute('GET', '/user', 'handler2');
|
||||
};
|
||||
|
||||
$cases[] = ['GET', '/user', $callback, 'handler2', []];
|
||||
|
||||
// 23 ----
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('*', '/user', 'handler0');
|
||||
$r->addRoute('GET', '/user', 'handler1');
|
||||
};
|
||||
|
||||
$cases[] = ['POST', '/user', $callback, 'handler0', []];
|
||||
|
||||
// 24 ----
|
||||
|
||||
$cases[] = ['HEAD', '/user', $callback, 'handler1', []];
|
||||
|
||||
// 25 ----
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/{bar}', 'handler0');
|
||||
$r->addRoute('*', '/foo', 'handler1');
|
||||
};
|
||||
|
||||
$cases[] = ['GET', '/foo', $callback, 'handler0', ['bar' => 'foo']];
|
||||
|
||||
// 26 ----
|
||||
|
||||
$callback = function(RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user', 'handler0');
|
||||
$r->addRoute('*', '/{foo:.*}', 'handler1');
|
||||
};
|
||||
|
||||
$cases[] = ['POST', '/bar', $callback, 'handler1', ['foo' => 'bar']];
|
||||
|
||||
// x -------------------------------------------------------------------------------------->
|
||||
|
||||
return $cases;
|
||||
}
|
||||
|
||||
public function provideNotFoundDispatchCases()
|
||||
{
|
||||
$cases = [];
|
||||
|
||||
// 0 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/resource/123/456', 'handler0');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/not-found';
|
||||
|
||||
$cases[] = [$method, $uri, $callback];
|
||||
|
||||
// 1 -------------------------------------------------------------------------------------->
|
||||
|
||||
// reuse callback from #0
|
||||
$method = 'POST';
|
||||
$uri = '/not-found';
|
||||
|
||||
$cases[] = [$method, $uri, $callback];
|
||||
|
||||
// 2 -------------------------------------------------------------------------------------->
|
||||
|
||||
// reuse callback from #1
|
||||
$method = 'PUT';
|
||||
$uri = '/not-found';
|
||||
|
||||
$cases[] = [$method, $uri, $callback];
|
||||
|
||||
// 3 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/handler0', 'handler0');
|
||||
$r->addRoute('GET', '/handler1', 'handler1');
|
||||
$r->addRoute('GET', '/handler2', 'handler2');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/not-found';
|
||||
|
||||
$cases[] = [$method, $uri, $callback];
|
||||
|
||||
// 4 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
|
||||
$r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1');
|
||||
$r->addRoute('GET', '/user/{name}', 'handler2');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/not-found';
|
||||
|
||||
$cases[] = [$method, $uri, $callback];
|
||||
|
||||
// 5 -------------------------------------------------------------------------------------->
|
||||
|
||||
// reuse callback from #4
|
||||
$method = 'GET';
|
||||
$uri = '/user/rdlowrey/12345/not-found';
|
||||
|
||||
$cases[] = [$method, $uri, $callback];
|
||||
|
||||
// 6 -------------------------------------------------------------------------------------->
|
||||
|
||||
// reuse callback from #5
|
||||
$method = 'HEAD';
|
||||
|
||||
$cases[] = [$method, $uri, $callback];
|
||||
|
||||
// x -------------------------------------------------------------------------------------->
|
||||
|
||||
return $cases;
|
||||
}
|
||||
|
||||
public function provideMethodNotAllowedDispatchCases()
|
||||
{
|
||||
$cases = [];
|
||||
|
||||
// 0 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/resource/123/456', 'handler0');
|
||||
};
|
||||
|
||||
$method = 'POST';
|
||||
$uri = '/resource/123/456';
|
||||
$allowedMethods = ['GET'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $allowedMethods];
|
||||
|
||||
// 1 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/resource/123/456', 'handler0');
|
||||
$r->addRoute('POST', '/resource/123/456', 'handler1');
|
||||
$r->addRoute('PUT', '/resource/123/456', 'handler2');
|
||||
$r->addRoute('*', '/', 'handler3');
|
||||
};
|
||||
|
||||
$method = 'DELETE';
|
||||
$uri = '/resource/123/456';
|
||||
$allowedMethods = ['GET', 'POST', 'PUT'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $allowedMethods];
|
||||
|
||||
// 2 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0');
|
||||
$r->addRoute('POST', '/user/{name}/{id:[0-9]+}', 'handler1');
|
||||
$r->addRoute('PUT', '/user/{name}/{id:[0-9]+}', 'handler2');
|
||||
$r->addRoute('PATCH', '/user/{name}/{id:[0-9]+}', 'handler3');
|
||||
};
|
||||
|
||||
$method = 'DELETE';
|
||||
$uri = '/user/rdlowrey/42';
|
||||
$allowedMethods = ['GET', 'POST', 'PUT', 'PATCH'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $allowedMethods];
|
||||
|
||||
// 3 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('POST', '/user/{name}', 'handler1');
|
||||
$r->addRoute('PUT', '/user/{name:[a-z]+}', 'handler2');
|
||||
$r->addRoute('PATCH', '/user/{name:[a-z]+}', 'handler3');
|
||||
};
|
||||
|
||||
$method = 'GET';
|
||||
$uri = '/user/rdlowrey';
|
||||
$allowedMethods = ['POST', 'PUT', 'PATCH'];
|
||||
|
||||
$cases[] = [$method, $uri, $callback, $allowedMethods];
|
||||
|
||||
// 4 -------------------------------------------------------------------------------------->
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost');
|
||||
$r->addRoute(['DELETE'], '/user', 'handlerDelete');
|
||||
$r->addRoute([], '/user', 'handlerNone');
|
||||
};
|
||||
|
||||
$cases[] = ['PUT', '/user', $callback, ['GET', 'POST', 'DELETE']];
|
||||
|
||||
// 5
|
||||
|
||||
$callback = function (RouteCollector $r) {
|
||||
$r->addRoute('POST', '/user.json', 'handler0');
|
||||
$r->addRoute('GET', '/{entity}.json', 'handler1');
|
||||
};
|
||||
|
||||
$cases[] = ['PUT', '/user.json', $callback, ['POST', 'GET']];
|
||||
|
||||
// x -------------------------------------------------------------------------------------->
|
||||
|
||||
return $cases;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
class GroupCountBasedTest extends DispatcherTest
|
||||
{
|
||||
protected function getDispatcherClass()
|
||||
{
|
||||
return 'FastRoute\\Dispatcher\\GroupCountBased';
|
||||
}
|
||||
|
||||
protected function getDataGeneratorClass()
|
||||
{
|
||||
return 'FastRoute\\DataGenerator\\GroupCountBased';
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
class GroupPosBasedTest extends DispatcherTest
|
||||
{
|
||||
protected function getDispatcherClass()
|
||||
{
|
||||
return 'FastRoute\\Dispatcher\\GroupPosBased';
|
||||
}
|
||||
|
||||
protected function getDataGeneratorClass()
|
||||
{
|
||||
return 'FastRoute\\DataGenerator\\GroupPosBased';
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\Dispatcher;
|
||||
|
||||
class MarkBasedTest extends DispatcherTest
|
||||
{
|
||||
public function setUp()
|
||||
{
|
||||
preg_match('/(*MARK:A)a/', 'a', $matches);
|
||||
if (!isset($matches['MARK'])) {
|
||||
$this->markTestSkipped('PHP 5.6 required for MARK support');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDispatcherClass()
|
||||
{
|
||||
return 'FastRoute\\Dispatcher\\MarkBased';
|
||||
}
|
||||
|
||||
protected function getDataGeneratorClass()
|
||||
{
|
||||
return 'FastRoute\\DataGenerator\\MarkBased';
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class HackTypecheckerTest extends TestCase
|
||||
{
|
||||
const SERVER_ALREADY_RUNNING_CODE = 77;
|
||||
|
||||
public function testTypechecks($recurse = true)
|
||||
{
|
||||
if (!defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('HHVM only');
|
||||
}
|
||||
if (!version_compare(HHVM_VERSION, '3.9.0', '>=')) {
|
||||
$this->markTestSkipped('classname<T> requires HHVM 3.9+');
|
||||
}
|
||||
|
||||
// The typechecker recurses the whole tree, so it makes sure
|
||||
// that everything in fixtures/ is valid when this runs.
|
||||
|
||||
$output = [];
|
||||
$exit_code = null;
|
||||
exec(
|
||||
'hh_server --check ' . escapeshellarg(__DIR__ . '/../../') . ' 2>&1',
|
||||
$output,
|
||||
$exit_code
|
||||
);
|
||||
if ($exit_code === self::SERVER_ALREADY_RUNNING_CODE) {
|
||||
$this->assertTrue(
|
||||
$recurse,
|
||||
'Typechecker still running after running hh_client stop'
|
||||
);
|
||||
// Server already running - 3.10 => 3.11 regression:
|
||||
// https://github.com/facebook/hhvm/issues/6646
|
||||
exec('hh_client stop 2>/dev/null');
|
||||
$this->testTypechecks(/* recurse = */ false);
|
||||
return;
|
||||
|
||||
}
|
||||
$this->assertSame(0, $exit_code, implode("\n", $output));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?hh
|
||||
|
||||
namespace FastRoute\TestFixtures;
|
||||
|
||||
function all_options_simple(): \FastRoute\Dispatcher {
|
||||
return \FastRoute\simpleDispatcher(
|
||||
$collector ==> {},
|
||||
shape(
|
||||
'routeParser' => \FastRoute\RouteParser\Std::class,
|
||||
'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class,
|
||||
'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class,
|
||||
'routeCollector' => \FastRoute\RouteCollector::class,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function all_options_cached(): \FastRoute\Dispatcher {
|
||||
return \FastRoute\cachedDispatcher(
|
||||
$collector ==> {},
|
||||
shape(
|
||||
'routeParser' => \FastRoute\RouteParser\Std::class,
|
||||
'dataGenerator' => \FastRoute\DataGenerator\GroupCountBased::class,
|
||||
'dispatcher' => \FastRoute\Dispatcher\GroupCountBased::class,
|
||||
'routeCollector' => \FastRoute\RouteCollector::class,
|
||||
'cacheFile' => '/dev/null',
|
||||
'cacheDisabled' => false,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?hh
|
||||
|
||||
namespace FastRoute\TestFixtures;
|
||||
|
||||
function empty_options_simple(): \FastRoute\Dispatcher {
|
||||
return \FastRoute\simpleDispatcher($collector ==> {}, shape());
|
||||
}
|
||||
|
||||
function empty_options_cached(): \FastRoute\Dispatcher {
|
||||
return \FastRoute\cachedDispatcher($collector ==> {}, shape());
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?hh
|
||||
|
||||
namespace FastRoute\TestFixtures;
|
||||
|
||||
function no_options_simple(): \FastRoute\Dispatcher {
|
||||
return \FastRoute\simpleDispatcher($collector ==> {});
|
||||
}
|
||||
|
||||
function no_options_cached(): \FastRoute\Dispatcher {
|
||||
return \FastRoute\cachedDispatcher($collector ==> {});
|
||||
}
|
||||
108
vendor/nikic/fast-route/test/RouteCollectorTest.php
vendored
108
vendor/nikic/fast-route/test/RouteCollectorTest.php
vendored
@@ -1,108 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RouteCollectorTest extends TestCase
|
||||
{
|
||||
public function testShortcuts()
|
||||
{
|
||||
$r = new DummyRouteCollector();
|
||||
|
||||
$r->delete('/delete', 'delete');
|
||||
$r->get('/get', 'get');
|
||||
$r->head('/head', 'head');
|
||||
$r->patch('/patch', 'patch');
|
||||
$r->post('/post', 'post');
|
||||
$r->put('/put', 'put');
|
||||
|
||||
$expected = [
|
||||
['DELETE', '/delete', 'delete'],
|
||||
['GET', '/get', 'get'],
|
||||
['HEAD', '/head', 'head'],
|
||||
['PATCH', '/patch', 'patch'],
|
||||
['POST', '/post', 'post'],
|
||||
['PUT', '/put', 'put'],
|
||||
];
|
||||
|
||||
$this->assertSame($expected, $r->routes);
|
||||
}
|
||||
|
||||
public function testGroups()
|
||||
{
|
||||
$r = new DummyRouteCollector();
|
||||
|
||||
$r->delete('/delete', 'delete');
|
||||
$r->get('/get', 'get');
|
||||
$r->head('/head', 'head');
|
||||
$r->patch('/patch', 'patch');
|
||||
$r->post('/post', 'post');
|
||||
$r->put('/put', 'put');
|
||||
|
||||
$r->addGroup('/group-one', function (DummyRouteCollector $r) {
|
||||
$r->delete('/delete', 'delete');
|
||||
$r->get('/get', 'get');
|
||||
$r->head('/head', 'head');
|
||||
$r->patch('/patch', 'patch');
|
||||
$r->post('/post', 'post');
|
||||
$r->put('/put', 'put');
|
||||
|
||||
$r->addGroup('/group-two', function (DummyRouteCollector $r) {
|
||||
$r->delete('/delete', 'delete');
|
||||
$r->get('/get', 'get');
|
||||
$r->head('/head', 'head');
|
||||
$r->patch('/patch', 'patch');
|
||||
$r->post('/post', 'post');
|
||||
$r->put('/put', 'put');
|
||||
});
|
||||
});
|
||||
|
||||
$r->addGroup('/admin', function (DummyRouteCollector $r) {
|
||||
$r->get('-some-info', 'admin-some-info');
|
||||
});
|
||||
$r->addGroup('/admin-', function (DummyRouteCollector $r) {
|
||||
$r->get('more-info', 'admin-more-info');
|
||||
});
|
||||
|
||||
$expected = [
|
||||
['DELETE', '/delete', 'delete'],
|
||||
['GET', '/get', 'get'],
|
||||
['HEAD', '/head', 'head'],
|
||||
['PATCH', '/patch', 'patch'],
|
||||
['POST', '/post', 'post'],
|
||||
['PUT', '/put', 'put'],
|
||||
['DELETE', '/group-one/delete', 'delete'],
|
||||
['GET', '/group-one/get', 'get'],
|
||||
['HEAD', '/group-one/head', 'head'],
|
||||
['PATCH', '/group-one/patch', 'patch'],
|
||||
['POST', '/group-one/post', 'post'],
|
||||
['PUT', '/group-one/put', 'put'],
|
||||
['DELETE', '/group-one/group-two/delete', 'delete'],
|
||||
['GET', '/group-one/group-two/get', 'get'],
|
||||
['HEAD', '/group-one/group-two/head', 'head'],
|
||||
['PATCH', '/group-one/group-two/patch', 'patch'],
|
||||
['POST', '/group-one/group-two/post', 'post'],
|
||||
['PUT', '/group-one/group-two/put', 'put'],
|
||||
['GET', '/admin-some-info', 'admin-some-info'],
|
||||
['GET', '/admin-more-info', 'admin-more-info'],
|
||||
];
|
||||
|
||||
$this->assertSame($expected, $r->routes);
|
||||
}
|
||||
}
|
||||
|
||||
class DummyRouteCollector extends RouteCollector
|
||||
{
|
||||
public $routes = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function addRoute($method, $route, $handler)
|
||||
{
|
||||
$route = $this->currentGroupPrefix . $route;
|
||||
$this->routes[] = [$method, $route, $handler];
|
||||
}
|
||||
}
|
||||
154
vendor/nikic/fast-route/test/RouteParser/StdTest.php
vendored
154
vendor/nikic/fast-route/test/RouteParser/StdTest.php
vendored
@@ -1,154 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace FastRoute\RouteParser;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class StdTest extends TestCase
|
||||
{
|
||||
/** @dataProvider provideTestParse */
|
||||
public function testParse($routeString, $expectedRouteDatas)
|
||||
{
|
||||
$parser = new Std();
|
||||
$routeDatas = $parser->parse($routeString);
|
||||
$this->assertSame($expectedRouteDatas, $routeDatas);
|
||||
}
|
||||
|
||||
/** @dataProvider provideTestParseError */
|
||||
public function testParseError($routeString, $expectedExceptionMessage)
|
||||
{
|
||||
$parser = new Std();
|
||||
$this->setExpectedException('FastRoute\\BadRouteException', $expectedExceptionMessage);
|
||||
$parser->parse($routeString);
|
||||
}
|
||||
|
||||
public function provideTestParse()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'/test',
|
||||
[
|
||||
['/test'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'/test/{param}',
|
||||
[
|
||||
['/test/', ['param', '[^/]+']],
|
||||
]
|
||||
],
|
||||
[
|
||||
'/te{ param }st',
|
||||
[
|
||||
['/te', ['param', '[^/]+'], 'st']
|
||||
]
|
||||
],
|
||||
[
|
||||
'/test/{param1}/test2/{param2}',
|
||||
[
|
||||
['/test/', ['param1', '[^/]+'], '/test2/', ['param2', '[^/]+']]
|
||||
]
|
||||
],
|
||||
[
|
||||
'/test/{param:\d+}',
|
||||
[
|
||||
['/test/', ['param', '\d+']]
|
||||
]
|
||||
],
|
||||
[
|
||||
'/test/{ param : \d{1,9} }',
|
||||
[
|
||||
['/test/', ['param', '\d{1,9}']]
|
||||
]
|
||||
],
|
||||
[
|
||||
'/test[opt]',
|
||||
[
|
||||
['/test'],
|
||||
['/testopt'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'/test[/{param}]',
|
||||
[
|
||||
['/test'],
|
||||
['/test/', ['param', '[^/]+']],
|
||||
]
|
||||
],
|
||||
[
|
||||
'/{param}[opt]',
|
||||
[
|
||||
['/', ['param', '[^/]+']],
|
||||
['/', ['param', '[^/]+'], 'opt']
|
||||
]
|
||||
],
|
||||
[
|
||||
'/test[/{name}[/{id:[0-9]+}]]',
|
||||
[
|
||||
['/test'],
|
||||
['/test/', ['name', '[^/]+']],
|
||||
['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']],
|
||||
]
|
||||
],
|
||||
[
|
||||
'',
|
||||
[
|
||||
[''],
|
||||
]
|
||||
],
|
||||
[
|
||||
'[test]',
|
||||
[
|
||||
[''],
|
||||
['test'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'/{foo-bar}',
|
||||
[
|
||||
['/', ['foo-bar', '[^/]+']]
|
||||
]
|
||||
],
|
||||
[
|
||||
'/{_foo:.*}',
|
||||
[
|
||||
['/', ['_foo', '.*']]
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function provideTestParseError()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'/test[opt',
|
||||
"Number of opening '[' and closing ']' does not match"
|
||||
],
|
||||
[
|
||||
'/test[opt[opt2]',
|
||||
"Number of opening '[' and closing ']' does not match"
|
||||
],
|
||||
[
|
||||
'/testopt]',
|
||||
"Number of opening '[' and closing ']' does not match"
|
||||
],
|
||||
[
|
||||
'/test[]',
|
||||
'Empty optional part'
|
||||
],
|
||||
[
|
||||
'/test[[opt]]',
|
||||
'Empty optional part'
|
||||
],
|
||||
[
|
||||
'[[test]]',
|
||||
'Empty optional part'
|
||||
],
|
||||
[
|
||||
'/test[/opt]/required',
|
||||
'Optional segments can only occur at the end of a route'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
11
vendor/nikic/fast-route/test/bootstrap.php
vendored
11
vendor/nikic/fast-route/test/bootstrap.php
vendored
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../src/functions.php';
|
||||
|
||||
spl_autoload_register(function ($class) {
|
||||
if (strpos($class, 'FastRoute\\') === 0) {
|
||||
$dir = strcasecmp(substr($class, -4), 'Test') ? 'src/' : 'test/';
|
||||
$name = substr($class, strlen('FastRoute'));
|
||||
require __DIR__ . '/../' . $dir . strtr($name, '\\', DIRECTORY_SEPARATOR) . '.php';
|
||||
}
|
||||
});
|
||||
36
vendor/psr/http-message/CHANGELOG.md
vendored
36
vendor/psr/http-message/CHANGELOG.md
vendored
@@ -1,36 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file, in reverse chronological order by release.
|
||||
|
||||
## 1.0.1 - 2016-08-06
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Updated all `@return self` annotation references in interfaces to use
|
||||
`@return static`, which more closelly follows the semantics of the
|
||||
specification.
|
||||
- Updated the `MessageInterface::getHeaders()` return annotation to use the
|
||||
value `string[][]`, indicating the format is a nested array of strings.
|
||||
- Updated the `@link` annotation for `RequestInterface::withRequestTarget()`
|
||||
to point to the correct section of RFC 7230.
|
||||
- Updated the `ServerRequestInterface::withUploadedFiles()` parameter annotation
|
||||
to add the parameter name (`$uploadedFiles`).
|
||||
- Updated a `@throws` annotation for the `UploadedFileInterface::moveTo()`
|
||||
method to correctly reference the method parameter (it was referencing an
|
||||
incorrect parameter name previously).
|
||||
|
||||
## 1.0.0 - 2016-05-18
|
||||
|
||||
Initial stable release; reflects accepted PSR-7 specification.
|
||||
19
vendor/psr/http-message/LICENSE
vendored
19
vendor/psr/http-message/LICENSE
vendored
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2014 PHP Framework Interoperability Group
|
||||
|
||||
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.
|
||||
16
vendor/psr/http-message/README.md
vendored
16
vendor/psr/http-message/README.md
vendored
@@ -1,16 +0,0 @@
|
||||
PSR Http Message
|
||||
================
|
||||
|
||||
This repository holds all interfaces/classes/traits related to
|
||||
[PSR-7](http://www.php-fig.org/psr/psr-7/).
|
||||
|
||||
Note that this is not a HTTP message implementation of its own. It is merely an
|
||||
interface that describes a HTTP message. See the specification for more details.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Before reading the usage guide we recommend reading the PSR-7 interfaces method list:
|
||||
|
||||
* [`PSR-7 Interfaces Method List`](docs/PSR7-Interfaces.md)
|
||||
* [`PSR-7 Usage Guide`](docs/PSR7-Usage.md)
|
||||
26
vendor/psr/http-message/composer.json
vendored
26
vendor/psr/http-message/composer.json
vendored
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"description": "Common interface for HTTP messages",
|
||||
"keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
|
||||
"homepage": "https://github.com/php-fig/http-message",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
130
vendor/psr/http-message/docs/PSR7-Interfaces.md
vendored
130
vendor/psr/http-message/docs/PSR7-Interfaces.md
vendored
@@ -1,130 +0,0 @@
|
||||
# Interfaces
|
||||
|
||||
The purpose of this list is to help in finding the methods when working with PSR-7. This can be considered as a cheatsheet for PSR-7 interfaces.
|
||||
|
||||
The interfaces defined in PSR-7 are the following:
|
||||
|
||||
| Class Name | Description |
|
||||
|---|---|
|
||||
| [Psr\Http\Message\MessageInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagemessageinterface) | Representation of a HTTP message |
|
||||
| [Psr\Http\Message\RequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagerequestinterface) | Representation of an outgoing, client-side request. |
|
||||
| [Psr\Http\Message\ServerRequestInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageserverrequestinterface) | Representation of an incoming, server-side HTTP request. |
|
||||
| [Psr\Http\Message\ResponseInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageresponseinterface) | Representation of an outgoing, server-side response. |
|
||||
| [Psr\Http\Message\StreamInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessagestreaminterface) | Describes a data stream |
|
||||
| [Psr\Http\Message\UriInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuriinterface) | Value object representing a URI. |
|
||||
| [Psr\Http\Message\UploadedFileInterface](http://www.php-fig.org/psr/psr-7/#psrhttpmessageuploadedfileinterface) | Value object representing a file uploaded through an HTTP request. |
|
||||
|
||||
## `Psr\Http\Message\MessageInterface` Methods
|
||||
|
||||
| Method Name | Description | Notes |
|
||||
|------------------------------------| ----------- | ----- |
|
||||
| `getProtocolVersion()` | Retrieve HTTP protocol version | 1.0 or 1.1 |
|
||||
| `withProtocolVersion($version)` | Returns new message instance with given HTTP protocol version | |
|
||||
| `getHeaders()` | Retrieve all HTTP Headers | [Request Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields), [Response Header List](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields) |
|
||||
| `hasHeader($name)` | Checks if HTTP Header with given name exists | |
|
||||
| `getHeader($name)` | Retrieves a array with the values for a single header | |
|
||||
| `getHeaderLine($name)` | Retrieves a comma-separated string of the values for a single header | |
|
||||
| `withHeader($name, $value)` | Returns new message instance with given HTTP Header | if the header existed in the original instance, replaces the header value from the original message with the value provided when creating the new instance. |
|
||||
| `withAddedHeader($name, $value)` | Returns new message instance with appended value to given header | If header already exists value will be appended, if not a new header will be created |
|
||||
| `withoutHeader($name)` | Removes HTTP Header with given name| |
|
||||
| `getBody()` | Retrieves the HTTP Message Body | Returns object implementing `StreamInterface`|
|
||||
| `withBody(StreamInterface $body)` | Returns new message instance with given HTTP Message Body | |
|
||||
|
||||
|
||||
## `Psr\Http\Message\RequestInterface` Methods
|
||||
|
||||
Same methods as `Psr\Http\Message\MessageInterface` + the following methods:
|
||||
|
||||
| Method Name | Description | Notes |
|
||||
|------------------------------------| ----------- | ----- |
|
||||
| `getRequestTarget()` | Retrieves the message's request target | origin-form, absolute-form, authority-form, asterisk-form ([RFC7230](https://www.rfc-editor.org/rfc/rfc7230.txt)) |
|
||||
| `withRequestTarget($requestTarget)` | Return a new message instance with the specific request-target | |
|
||||
| `getMethod()` | Retrieves the HTTP method of the request. | GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE (defined in [RFC7231](https://tools.ietf.org/html/rfc7231)), PATCH (defined in [RFC5789](https://tools.ietf.org/html/rfc5789)) |
|
||||
| `withMethod($method)` | Returns a new message instance with the provided HTTP method | |
|
||||
| `getUri()` | Retrieves the URI instance | |
|
||||
| `withUri(UriInterface $uri, $preserveHost = false)` | Returns a new message instance with the provided URI | |
|
||||
|
||||
|
||||
## `Psr\Http\Message\ServerRequestInterface` Methods
|
||||
|
||||
Same methods as `Psr\Http\Message\RequestInterface` + the following methods:
|
||||
|
||||
| Method Name | Description | Notes |
|
||||
|------------------------------------| ----------- | ----- |
|
||||
| `getServerParams() ` | Retrieve server parameters | Typically derived from `$_SERVER` |
|
||||
| `getCookieParams()` | Retrieves cookies sent by the client to the server. | Typically derived from `$_COOKIES` |
|
||||
| `withCookieParams(array $cookies)` | Returns a new request instance with the specified cookies | |
|
||||
| `withQueryParams(array $query)` | Returns a new request instance with the specified query string arguments | |
|
||||
| `getUploadedFiles()` | Retrieve normalized file upload data | |
|
||||
| `withUploadedFiles(array $uploadedFiles)` | Returns a new request instance with the specified uploaded files | |
|
||||
| `getParsedBody()` | Retrieve any parameters provided in the request body | |
|
||||
| `withParsedBody($data)` | Returns a new request instance with the specified body parameters | |
|
||||
| `getAttributes()` | Retrieve attributes derived from the request | |
|
||||
| `getAttribute($name, $default = null)` | Retrieve a single derived request attribute | |
|
||||
| `withAttribute($name, $value)` | Returns a new request instance with the specified derived request attribute | |
|
||||
| `withoutAttribute($name)` | Returns a new request instance that without the specified derived request attribute | |
|
||||
|
||||
## `Psr\Http\Message\ResponseInterface` Methods:
|
||||
|
||||
Same methods as `Psr\Http\Message\MessageInterface` + the following methods:
|
||||
|
||||
| Method Name | Description | Notes |
|
||||
|------------------------------------| ----------- | ----- |
|
||||
| `getStatusCode()` | Gets the response status code. | |
|
||||
| `withStatus($code, $reasonPhrase = '')` | Returns a new response instance with the specified status code and, optionally, reason phrase. | |
|
||||
| `getReasonPhrase()` | Gets the response reason phrase associated with the status code. | |
|
||||
|
||||
## `Psr\Http\Message\StreamInterface` Methods
|
||||
|
||||
| Method Name | Description | Notes |
|
||||
|------------------------------------| ----------- | ----- |
|
||||
| `__toString()` | Reads all data from the stream into a string, from the beginning to end. | |
|
||||
| `close()` | Closes the stream and any underlying resources. | |
|
||||
| `detach()` | Separates any underlying resources from the stream. | |
|
||||
| `getSize()` | Get the size of the stream if known. | |
|
||||
| `eof()` | Returns true if the stream is at the end of the stream.| |
|
||||
| `isSeekable()` | Returns whether or not the stream is seekable. | |
|
||||
| `seek($offset, $whence = SEEK_SET)` | Seek to a position in the stream. | |
|
||||
| `rewind()` | Seek to the beginning of the stream. | |
|
||||
| `isWritable()` | Returns whether or not the stream is writable. | |
|
||||
| `write($string)` | Write data to the stream. | |
|
||||
| `isReadable()` | Returns whether or not the stream is readable. | |
|
||||
| `read($length)` | Read data from the stream. | |
|
||||
| `getContents()` | Returns the remaining contents in a string | |
|
||||
| `getMetadata($key = null)()` | Get stream metadata as an associative array or retrieve a specific key. | |
|
||||
|
||||
## `Psr\Http\Message\UriInterface` Methods
|
||||
|
||||
| Method Name | Description | Notes |
|
||||
|------------------------------------| ----------- | ----- |
|
||||
| `getScheme()` | Retrieve the scheme component of the URI. | |
|
||||
| `getAuthority()` | Retrieve the authority component of the URI. | |
|
||||
| `getUserInfo()` | Retrieve the user information component of the URI. | |
|
||||
| `getHost()` | Retrieve the host component of the URI. | |
|
||||
| `getPort()` | Retrieve the port component of the URI. | |
|
||||
| `getPath()` | Retrieve the path component of the URI. | |
|
||||
| `getQuery()` | Retrieve the query string of the URI. | |
|
||||
| `getFragment()` | Retrieve the fragment component of the URI. | |
|
||||
| `withScheme($scheme)` | Return an instance with the specified scheme. | |
|
||||
| `withUserInfo($user, $password = null)` | Return an instance with the specified user information. | |
|
||||
| `withHost($host)` | Return an instance with the specified host. | |
|
||||
| `withPort($port)` | Return an instance with the specified port. | |
|
||||
| `withPath($path)` | Return an instance with the specified path. | |
|
||||
| `withQuery($query)` | Return an instance with the specified query string. | |
|
||||
| `withFragment($fragment)` | Return an instance with the specified URI fragment. | |
|
||||
| `__toString()` | Return the string representation as a URI reference. | |
|
||||
|
||||
## `Psr\Http\Message\UploadedFileInterface` Methods
|
||||
|
||||
| Method Name | Description | Notes |
|
||||
|------------------------------------| ----------- | ----- |
|
||||
| `getStream()` | Retrieve a stream representing the uploaded file. | |
|
||||
| `moveTo($targetPath)` | Move the uploaded file to a new location. | |
|
||||
| `getSize()` | Retrieve the file size. | |
|
||||
| `getError()` | Retrieve the error associated with the uploaded file. | |
|
||||
| `getClientFilename()` | Retrieve the filename sent by the client. | |
|
||||
| `getClientMediaType()` | Retrieve the media type sent by the client. | |
|
||||
|
||||
> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`.
|
||||
> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.
|
||||
|
||||
159
vendor/psr/http-message/docs/PSR7-Usage.md
vendored
159
vendor/psr/http-message/docs/PSR7-Usage.md
vendored
@@ -1,159 +0,0 @@
|
||||
### PSR-7 Usage
|
||||
|
||||
All PSR-7 applications comply with these interfaces
|
||||
They were created to establish a standard between middleware implementations.
|
||||
|
||||
> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`.
|
||||
> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered.
|
||||
|
||||
|
||||
The following examples will illustrate how basic operations are done in PSR-7.
|
||||
|
||||
##### Examples
|
||||
|
||||
|
||||
For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc)
|
||||
All PSR-7 implementations should have the same behaviour.
|
||||
|
||||
The following will be assumed:
|
||||
`$request` is an object of `Psr\Http\Message\RequestInterface` and
|
||||
|
||||
`$response` is an object implementing `Psr\Http\Message\RequestInterface`
|
||||
|
||||
|
||||
### Working with HTTP Headers
|
||||
|
||||
#### Adding headers to response:
|
||||
|
||||
```php
|
||||
$response->withHeader('My-Custom-Header', 'My Custom Message');
|
||||
```
|
||||
|
||||
#### Appending values to headers
|
||||
|
||||
```php
|
||||
$response->withAddedHeader('My-Custom-Header', 'The second message');
|
||||
```
|
||||
|
||||
#### Checking if header exists:
|
||||
|
||||
```php
|
||||
$request->hasHeader('My-Custom-Header'); // will return false
|
||||
$response->hasHeader('My-Custom-Header'); // will return true
|
||||
```
|
||||
|
||||
> Note: My-Custom-Header was only added in the Response
|
||||
|
||||
#### Getting comma-separated values from a header (also applies to request)
|
||||
|
||||
```php
|
||||
// getting value from request headers
|
||||
$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8"
|
||||
// getting value from response headers
|
||||
$response->getHeaderLine('My-Custom-Header'); // will return: "My Custom Message; The second message"
|
||||
```
|
||||
|
||||
#### Getting array of value from a header (also applies to request)
|
||||
```php
|
||||
// getting value from request headers
|
||||
$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"]
|
||||
// getting value from response headers
|
||||
$response->getHeader('My-Custom-Header'); // will return: ["My Custom Message", "The second message"]
|
||||
```
|
||||
|
||||
#### Removing headers from HTTP Messages
|
||||
```php
|
||||
// removing a header from Request, removing deprecated "Content-MD5" header
|
||||
$request->withoutHeader('Content-MD5');
|
||||
|
||||
// removing a header from Response
|
||||
// effect: the browser won't know the size of the stream
|
||||
// the browser will download the stream till it ends
|
||||
$response->withoutHeader('Content-Length');
|
||||
```
|
||||
|
||||
### Working with HTTP Message Body
|
||||
|
||||
When working with the PSR-7 there are two methods of implementation:
|
||||
#### 1. Getting the body separately
|
||||
|
||||
> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented.
|
||||
|
||||
```php
|
||||
$body = $response->getBody();
|
||||
// operations on body, eg. read, write, seek
|
||||
// ...
|
||||
// replacing the old body
|
||||
$response->withBody($body);
|
||||
// this last statement is optional as we working with objects
|
||||
// in this case the "new" body is same with the "old" one
|
||||
// the $body variable has the same value as the one in $request, only the reference is passed
|
||||
```
|
||||
|
||||
#### 2. Working directly on response
|
||||
|
||||
> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required
|
||||
|
||||
```php
|
||||
$response->getBody()->write('hello');
|
||||
```
|
||||
|
||||
### Getting the body contents
|
||||
|
||||
The following snippet gets the contents of a stream contents.
|
||||
> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream.
|
||||
```php
|
||||
$body = $response->getBody();
|
||||
$body->rewind(); // or $body->seek(0);
|
||||
$bodyText = $body->getContents();
|
||||
```
|
||||
> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended.
|
||||
|
||||
### Append to body
|
||||
|
||||
```php
|
||||
$response->getBody()->write('Hello'); // writing directly
|
||||
$body = $request->getBody(); // which is a `StreamInterface`
|
||||
$body->write('xxxxx');
|
||||
```
|
||||
|
||||
### Prepend to body
|
||||
Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended.
|
||||
The following example will explain the behaviour of streams.
|
||||
|
||||
```php
|
||||
// assuming our response is initially empty
|
||||
$body = $repsonse->getBody();
|
||||
// writing the string "abcd"
|
||||
$body->write('abcd');
|
||||
|
||||
// seeking to start of stream
|
||||
$body->seek(0);
|
||||
// writing 'ef'
|
||||
$body->write('ef'); // at this point the stream contains "efcd"
|
||||
```
|
||||
|
||||
#### Prepending by rewriting separately
|
||||
|
||||
```php
|
||||
// assuming our response body stream only contains: "abcd"
|
||||
$body = $response->getBody();
|
||||
$body->rewind();
|
||||
$contents = $body->getContents(); // abcd
|
||||
// seeking the stream to beginning
|
||||
$body->rewind();
|
||||
$body->write('ef'); // stream contains "efcd"
|
||||
$body->write($contents); // stream contains "efabcd"
|
||||
```
|
||||
|
||||
> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`.
|
||||
|
||||
#### Prepending by using contents as a string
|
||||
```php
|
||||
$body = $response->getBody();
|
||||
$body->rewind();
|
||||
$contents = $body->getContents(); // efabcd
|
||||
$contents = 'ef'.$contents;
|
||||
$body->rewind();
|
||||
$body->write($contents);
|
||||
```
|
||||
189
vendor/psr/http-message/src/MessageInterface.php
vendored
189
vendor/psr/http-message/src/MessageInterface.php
vendored
@@ -1,189 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psr\Http\Message;
|
||||
|
||||
/**
|
||||
* HTTP messages consist of requests from a client to a server and responses
|
||||
* from a server to a client. This interface defines the methods common to
|
||||
* each.
|
||||
*
|
||||
* Messages are considered immutable; all methods that might change state MUST
|
||||
* be implemented such that they retain the internal state of the current
|
||||
* message and return an instance that contains the changed state.
|
||||
*
|
||||
* @link http://www.ietf.org/rfc/rfc7230.txt
|
||||
* @link http://www.ietf.org/rfc/rfc7231.txt
|
||||
*/
|
||||
interface MessageInterface
|
||||
{
|
||||
/**
|
||||
* Retrieves the HTTP protocol version as a string.
|
||||
*
|
||||
* The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
|
||||
*
|
||||
* @return string HTTP protocol version.
|
||||
*/
|
||||
public function getProtocolVersion();
|
||||
|
||||
/**
|
||||
* Return an instance with the specified HTTP protocol version.
|
||||
*
|
||||
* The version string MUST contain only the HTTP version number (e.g.,
|
||||
* "1.1", "1.0").
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new protocol version.
|
||||
*
|
||||
* @param string $version HTTP protocol version
|
||||
* @return static
|
||||
*/
|
||||
public function withProtocolVersion(string $version);
|
||||
|
||||
/**
|
||||
* Retrieves all message header values.
|
||||
*
|
||||
* The keys represent the header name as it will be sent over the wire, and
|
||||
* each value is an array of strings associated with the header.
|
||||
*
|
||||
* // Represent the headers as a string
|
||||
* foreach ($message->getHeaders() as $name => $values) {
|
||||
* echo $name . ": " . implode(", ", $values);
|
||||
* }
|
||||
*
|
||||
* // Emit headers iteratively:
|
||||
* foreach ($message->getHeaders() as $name => $values) {
|
||||
* foreach ($values as $value) {
|
||||
* header(sprintf('%s: %s', $name, $value), false);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* While header names are not case-sensitive, getHeaders() will preserve the
|
||||
* exact case in which headers were originally specified.
|
||||
*
|
||||
* @return string[][] Returns an associative array of the message's headers. Each
|
||||
* key MUST be a header name, and each value MUST be an array of strings
|
||||
* for that header.
|
||||
*/
|
||||
public function getHeaders();
|
||||
|
||||
/**
|
||||
* Checks if a header exists by the given case-insensitive name.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @return bool Returns true if any header names match the given header
|
||||
* name using a case-insensitive string comparison. Returns false if
|
||||
* no matching header name is found in the message.
|
||||
*/
|
||||
public function hasHeader(string $name);
|
||||
|
||||
/**
|
||||
* Retrieves a message header value by the given case-insensitive name.
|
||||
*
|
||||
* This method returns an array of all the header values of the given
|
||||
* case-insensitive header name.
|
||||
*
|
||||
* If the header does not appear in the message, this method MUST return an
|
||||
* empty array.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @return string[] An array of string values as provided for the given
|
||||
* header. If the header does not appear in the message, this method MUST
|
||||
* return an empty array.
|
||||
*/
|
||||
public function getHeader(string $name);
|
||||
|
||||
/**
|
||||
* Retrieves a comma-separated string of the values for a single header.
|
||||
*
|
||||
* This method returns all of the header values of the given
|
||||
* case-insensitive header name as a string concatenated together using
|
||||
* a comma.
|
||||
*
|
||||
* NOTE: Not all header values may be appropriately represented using
|
||||
* comma concatenation. For such headers, use getHeader() instead
|
||||
* and supply your own delimiter when concatenating.
|
||||
*
|
||||
* If the header does not appear in the message, this method MUST return
|
||||
* an empty string.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @return string A string of values as provided for the given header
|
||||
* concatenated together using a comma. If the header does not appear in
|
||||
* the message, this method MUST return an empty string.
|
||||
*/
|
||||
public function getHeaderLine(string $name);
|
||||
|
||||
/**
|
||||
* Return an instance with the provided value replacing the specified header.
|
||||
*
|
||||
* While header names are case-insensitive, the casing of the header will
|
||||
* be preserved by this function, and returned from getHeaders().
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new and/or updated header and value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @param string|string[] $value Header value(s).
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException for invalid header names or values.
|
||||
*/
|
||||
public function withHeader(string $name, $value);
|
||||
|
||||
/**
|
||||
* Return an instance with the specified header appended with the given value.
|
||||
*
|
||||
* Existing values for the specified header will be maintained. The new
|
||||
* value(s) will be appended to the existing list. If the header did not
|
||||
* exist previously, it will be added.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new header and/or value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name to add.
|
||||
* @param string|string[] $value Header value(s).
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException for invalid header names or values.
|
||||
*/
|
||||
public function withAddedHeader(string $name, $value);
|
||||
|
||||
/**
|
||||
* Return an instance without the specified header.
|
||||
*
|
||||
* Header resolution MUST be done without case-sensitivity.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that removes
|
||||
* the named header.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name to remove.
|
||||
* @return static
|
||||
*/
|
||||
public function withoutHeader(string $name);
|
||||
|
||||
/**
|
||||
* Gets the body of the message.
|
||||
*
|
||||
* @return StreamInterface Returns the body as a stream.
|
||||
*/
|
||||
public function getBody();
|
||||
|
||||
/**
|
||||
* Return an instance with the specified message body.
|
||||
*
|
||||
* The body MUST be a StreamInterface object.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return a new instance that has the
|
||||
* new body stream.
|
||||
*
|
||||
* @param StreamInterface $body Body.
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException When the body is not valid.
|
||||
*/
|
||||
public function withBody(StreamInterface $body);
|
||||
}
|
||||
131
vendor/psr/http-message/src/RequestInterface.php
vendored
131
vendor/psr/http-message/src/RequestInterface.php
vendored
@@ -1,131 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psr\Http\Message;
|
||||
|
||||
/**
|
||||
* Representation of an outgoing, client-side request.
|
||||
*
|
||||
* Per the HTTP specification, this interface includes properties for
|
||||
* each of the following:
|
||||
*
|
||||
* - Protocol version
|
||||
* - HTTP method
|
||||
* - URI
|
||||
* - Headers
|
||||
* - Message body
|
||||
*
|
||||
* During construction, implementations MUST attempt to set the Host header from
|
||||
* a provided URI if no Host header is provided.
|
||||
*
|
||||
* Requests are considered immutable; all methods that might change state MUST
|
||||
* be implemented such that they retain the internal state of the current
|
||||
* message and return an instance that contains the changed state.
|
||||
*/
|
||||
interface RequestInterface extends MessageInterface
|
||||
{
|
||||
/**
|
||||
* Retrieves the message's request target.
|
||||
*
|
||||
* Retrieves the message's request-target either as it will appear (for
|
||||
* clients), as it appeared at request (for servers), or as it was
|
||||
* specified for the instance (see withRequestTarget()).
|
||||
*
|
||||
* In most cases, this will be the origin-form of the composed URI,
|
||||
* unless a value was provided to the concrete implementation (see
|
||||
* withRequestTarget() below).
|
||||
*
|
||||
* If no URI is available, and no request-target has been specifically
|
||||
* provided, this method MUST return the string "/".
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRequestTarget();
|
||||
|
||||
/**
|
||||
* Return an instance with the specific request-target.
|
||||
*
|
||||
* If the request needs a non-origin-form request-target — e.g., for
|
||||
* specifying an absolute-form, authority-form, or asterisk-form —
|
||||
* this method may be used to create an instance with the specified
|
||||
* request-target, verbatim.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* changed request target.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
|
||||
* request-target forms allowed in request messages)
|
||||
* @param string $requestTarget
|
||||
* @return static
|
||||
*/
|
||||
public function withRequestTarget(string $requestTarget);
|
||||
|
||||
/**
|
||||
* Retrieves the HTTP method of the request.
|
||||
*
|
||||
* @return string Returns the request method.
|
||||
*/
|
||||
public function getMethod();
|
||||
|
||||
/**
|
||||
* Return an instance with the provided HTTP method.
|
||||
*
|
||||
* While HTTP method names are typically all uppercase characters, HTTP
|
||||
* method names are case-sensitive and thus implementations SHOULD NOT
|
||||
* modify the given string.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* changed request method.
|
||||
*
|
||||
* @param string $method Case-sensitive method.
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException for invalid HTTP methods.
|
||||
*/
|
||||
public function withMethod(string $method);
|
||||
|
||||
/**
|
||||
* Retrieves the URI instance.
|
||||
*
|
||||
* This method MUST return a UriInterface instance.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc3986#section-4.3
|
||||
* @return UriInterface Returns a UriInterface instance
|
||||
* representing the URI of the request.
|
||||
*/
|
||||
public function getUri();
|
||||
|
||||
/**
|
||||
* Returns an instance with the provided URI.
|
||||
*
|
||||
* This method MUST update the Host header of the returned request by
|
||||
* default if the URI contains a host component. If the URI does not
|
||||
* contain a host component, any pre-existing Host header MUST be carried
|
||||
* over to the returned request.
|
||||
*
|
||||
* You can opt-in to preserving the original state of the Host header by
|
||||
* setting `$preserveHost` to `true`. When `$preserveHost` is set to
|
||||
* `true`, this method interacts with the Host header in the following ways:
|
||||
*
|
||||
* - If the Host header is missing or empty, and the new URI contains
|
||||
* a host component, this method MUST update the Host header in the returned
|
||||
* request.
|
||||
* - If the Host header is missing or empty, and the new URI does not contain a
|
||||
* host component, this method MUST NOT update the Host header in the returned
|
||||
* request.
|
||||
* - If a Host header is present and non-empty, this method MUST NOT update
|
||||
* the Host header in the returned request.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new UriInterface instance.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc3986#section-4.3
|
||||
* @param UriInterface $uri New request URI to use.
|
||||
* @param bool $preserveHost Preserve the original state of the Host header.
|
||||
* @return static
|
||||
*/
|
||||
public function withUri(UriInterface $uri, bool $preserveHost = false);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Psr\Http\Message;
|
||||
|
||||
/**
|
||||
* Representation of an outgoing, server-side response.
|
||||
*
|
||||
* Per the HTTP specification, this interface includes properties for
|
||||
* each of the following:
|
||||
*
|
||||
* - Protocol version
|
||||
* - Status code and reason phrase
|
||||
* - Headers
|
||||
* - Message body
|
||||
*
|
||||
* Responses are considered immutable; all methods that might change state MUST
|
||||
* be implemented such that they retain the internal state of the current
|
||||
* message and return an instance that contains the changed state.
|
||||
*/
|
||||
interface ResponseInterface extends MessageInterface
|
||||
{
|
||||
/**
|
||||
* Gets the response status code.
|
||||
*
|
||||
* The status code is a 3-digit integer result code of the server's attempt
|
||||
* to understand and satisfy the request.
|
||||
*
|
||||
* @return int Status code.
|
||||
*/
|
||||
public function getStatusCode();
|
||||
|
||||
/**
|
||||
* Return an instance with the specified status code and, optionally, reason phrase.
|
||||
*
|
||||
* If no reason phrase is specified, implementations MAY choose to default
|
||||
* to the RFC 7231 or IANA recommended reason phrase for the response's
|
||||
* status code.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* updated status and reason phrase.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
* @param int $code The 3-digit integer result code to set.
|
||||
* @param string $reasonPhrase The reason phrase to use with the
|
||||
* provided status code; if none is provided, implementations MAY
|
||||
* use the defaults as suggested in the HTTP specification.
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException For invalid status code arguments.
|
||||
*/
|
||||
public function withStatus(int $code, string $reasonPhrase = '');
|
||||
|
||||
/**
|
||||
* Gets the response reason phrase associated with the status code.
|
||||
*
|
||||
* Because a reason phrase is not a required element in a response
|
||||
* status line, the reason phrase value MAY be null. Implementations MAY
|
||||
* choose to return the default RFC 7231 recommended reason phrase (or those
|
||||
* listed in the IANA HTTP Status Code Registry) for the response's
|
||||
* status code.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
* @return string Reason phrase; must return an empty string if none present.
|
||||
*/
|
||||
public function getReasonPhrase();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user